/
www
/
wwwroot
/
alo88.autos
/
wp-content
/
plugins
/
wp-content-crawler
/
app
/
Test
/
Base
/
Upload File
HOME
<?php /** * Created by PhpStorm. * User: turgutsaricam * Date: 25/10/2018 * Time: 15:19 */ namespace WPCCrawler\Test\Base; use Exception; use Illuminate\Contracts\View\View; use Illuminate\Support\Str; use Symfony\Component\DomCrawler\Crawler; use WPCCrawler\Factory; use WPCCrawler\Objects\Api\OpenAi\OpenAiClient; use WPCCrawler\Objects\Crawling\Bot\AbstractBot; use WPCCrawler\Objects\Crawling\Bot\DummyBot; use WPCCrawler\Objects\Crawling\Interfaces\MakesCrawlRequest; use WPCCrawler\Objects\Enums\InformationType; use WPCCrawler\Objects\Filtering\FilterDependencyProvider\Page\PageFilterDependencyProvider; use WPCCrawler\Objects\Informing\Information; use WPCCrawler\Objects\Informing\Informer; use WPCCrawler\Objects\OptionsBox\Boxes\Base\BaseOptionsBoxApplier; use WPCCrawler\Objects\OptionsBox\OptionsBoxService; use WPCCrawler\Objects\Settings\Factory\HtmlManip\CategoryHtmlManipKeyFactory; use WPCCrawler\Objects\Settings\Factory\HtmlManip\PostHtmlManipKeyFactory; use WPCCrawler\Test\Data\TestData; use WPCCrawler\Test\Enums\ManipulationStep; use WPCCrawler\Utils; use WPCCrawler\WPCCrawler; abstract class AbstractTest { /** @var string The key under which the view is added to the response */ protected $responseViewKey = 'view'; /** @var string The key under which the results are added to the response */ protected $responseResultsKey = 'data'; /** * @var string The name of the variable which stores the modified results, which are the results obtained after * applying the options configured in the options box, in the view. */ protected $viewModifiedResultsKey = 'modifiedResults'; /** @var float Start time of the test. */ private $startTime = 0; /** @var float Memory usage just before conducting the test. */ private $memoryInitial = 0; /** @var TestData */ private $data; /** @var array|null */ private $results = null; /** @var bool */ private $isRun = false; /** * @param TestData $data The data to be used to conduct the test */ public function __construct(TestData $data) { $this->data = $data; } /* * ABSTRACT METHODS */ /** * Conduct the test and return an array of results. * * @param TestData $data Information required for the test * @return array|null */ protected abstract function createResults($data): ?array; /** * Create the view of the response * * @return View|null */ protected abstract function createView(); /* * PUBLIC METHODS */ /** * Run the test * * @return $this */ public function run(): self { WPCCrawler::setDoingUnitTest(true); // When testing, if there is an OpenAI GPT short code, that short code is executed every time. This means that // the user must pay money just to run tests. To avoid this, replace the OpenAI client with a mock that returns // predefined responses. OpenAiClient::setTestInstance(null); OpenAiClient::setTestInstance(OpenAiClient::newInstance('') ->setUsePredefinedResponses(true) ); // Mark the start time and initial memory usage so that we can calculate elapsed time and memory usage later. $this->startTime = microtime(true); $this->memoryInitial = memory_get_usage(); try { $this->results = $this->createResults($this->data); } catch (Exception $e) { $this->results = []; Informer::addInfo($e->getMessage() ?: _wpcc('An error occurred during the test.')) ->setException($e)->addAsLog(); } $this->isRun = true; return $this; } /** * Get the test results * * @return array Test results as an array * @throws Exception If the test has not been run */ public function getResults(): array { $this->checkIfRunOnce(); return $this->results ?: []; } /** * Get JSON that shows the results. The results include an HTML view as well. * * @return string * @throws Exception If the test has not been run */ public function getResponse(): string { $this->checkIfRunOnce(); // Create the response $response = [ $this->responseResultsKey => $this->getResults() ?: [] ]; // If view exists, add it to the response. $view = $this->createView(); if ($view) { // If the test data does not come from the options box, apply available options box settings to the results. if (!$this->getData()->isFromOptionsBox() && $this->shouldApplyOptionsBoxOptions()) { $this->addOptionsBoxResultsToView($view); } // Add memory usage and elapsed time $view->with([ 'memoryUsage' => $this->calculateMemoryUsage(), 'elapsedTime' => $this->calculateElapsedTime() ]); // Render the view and add it to the response $response[$this->responseViewKey] = $view->render(); } else { // If there is no view, add information messages. $response['infoView'] = Utils::view('partials/info-list')->render(); $response['infoStyle'] = Factory::assetManager()->getInformationStyle(); $response['memoryUsage'] = $this->calculateMemoryUsage(); $response['elapsedTime'] = $this->calculateElapsedTime(); } // Return the response by parsing it to JSON $json = json_encode($response); // If there is an error when parsing the response to JSON, log it. if ($json === false) { Informer::add((new Information( sprintf(_wpcc('JSON encoding error (%1$s)'), json_last_error()), json_last_error_msg(), InformationType::ERROR ))->addAsLog()); // If the error was caused by malformed UTF-8 chars, try to convert the encoding and try again if (json_last_error() === JSON_ERROR_UTF8) { // If there is a view, render it so that the information can be shown in the view. if ($view) $response[$this->responseViewKey] = $view->render(); // Now, try to fix the encoding and try to parse it to JSON again. $response = Utils::deepFixMixedUTF8Encoding($response); $json = json_encode($response); // If there still is a problem if ($json === false) { // Remove the results and set the view as info view so that the user can see what went wrong. $response[$this->responseResultsKey] = []; $response[$this->responseViewKey] = Utils::view('partials/info-list')->render(); $json = json_encode($response); } } } return $json === false ? '' : $json; } /* * PROTECTED METHODS */ /** * @param AbstractBot|null $bot A bot. The data required for filters will be retrieved from this bot. * Hence, this must be the bot that makes the requests for the test, if * any. * @param Crawler|null $crawler * @param null|int $lastStep One of the constants of defined in {@link ManipulationStep}, e.g. * {@link ManipulationStep::REMOVE_ELEMENT_ATTRIBUTES}. If this is null, * all the manipulation steps will be applied. * @param null|string $fallbackBaseUrl See {@link AbstractBot::resolveRelativeUrls()} */ protected function applyHtmlManipulationOptions(?AbstractBot $bot, ?Crawler &$crawler, $lastStep = null, $fallbackBaseUrl = null): void { if (!$crawler) return; // Create a bot by adding the manipulation options over the bot's settings $botSettings = $bot ? $bot->getSettings() : []; $dummyBot = new DummyBot( $this->data->getManipulationOptions() + $botSettings, null, $this->data->getUseUtf8(), $this->data->getConvertEncodingToUtf8() ); if ($bot instanceof MakesCrawlRequest) { $dummyBot ->setResponseHttpStatusCode($bot->getResponseHttpStatusCode()) ->setCrawlingUrl($bot->getCrawlingUrl()); } if ($bot) { $dummyBot->setResponseCacheEnabled($bot->isResponseCacheEnabled()); } // Apply manipulation steps and stop at the last manipulation step $this->applyHtmlManipulationSteps($dummyBot, $crawler, $fallbackBaseUrl, $lastStep); } /** * @return bool|null True if the manipulation options are retrieved from post tab. If not, false. Null, if there is * no manipulation option. * @since 1.9.0 */ protected function isManipulationOptionsForPost(): ?bool { // Make sure there are manipulation options $manipulationOptions = $this->data->getManipulationOptions(); if (!$manipulationOptions) return null; // Make sure there are array keys $optionKeys = array_keys($manipulationOptions); if (!$optionKeys) return null; // @phpstan-ignore-line // Find out if this is for post or category return Str::startsWith($optionKeys[0], '_post'); } /** * Configure the options box applier. This is called before the applier applies the options. So, you can configure * the applier here. By default, all options will be applied. * * @param BaseOptionsBoxApplier $optionsBoxApplier */ protected function configureOptionsBoxApplier($optionsBoxApplier): void { } /** * @return bool `true` if the options configured in the options box must be applied to the results and added to the * view * @since 1.14.0 */ protected function shouldApplyOptionsBoxOptions(): bool { return true; } /* * GETTERS */ /** * @return TestData */ public function getData(): TestData { return $this->data; } /** * @return bool */ public function isRun(): bool { return $this->isRun; } /* * PRIVATE METHODS */ /** * @throws Exception If the test has not been run */ private function checkIfRunOnce(): void { if (!$this->isRun) { throw new Exception("The test has not been run. You have to run the test first."); } } /** * Applies options box options and adds the results to the given view under {@link $viewModifiedResultsKey} key. * * @param View $view * @throws Exception * @since 1.8.0 */ private function addOptionsBoxResultsToView($view): void { // Get options box data $baseOptionsBoxData = $this->data->getOptionsBoxData(); $optionsBoxData = $baseOptionsBoxData ? $baseOptionsBoxData->getData() : null; if (!$optionsBoxData) return; // Create an options box applier $optionsBoxApplier = OptionsBoxService::getInstance()->createApplierFromRawData($optionsBoxData); if (!$optionsBoxApplier) return; $optionsBoxApplier ->setForTest(true) ->setFromOptionsBox(false); // Let the child configure the options box applier. $this->configureOptionsBoxApplier($optionsBoxApplier); $modifiedResults = array_map(function ($v) use (&$optionsBoxApplier) { $res = $optionsBoxApplier->apply($v); if (is_object($res) || is_array($res)) return ''; return $res; }, $this->getResults()); // Remove null values from the modified results. $modifiedResults = array_filter($modifiedResults, function($v) { return $v !== null; }); // Add the modified results to the view $view->with($this->viewModifiedResultsKey, $modifiedResults ?: []); } /** * Applies manipulation steps and stops at the given last step * * @param AbstractBot $bot A bot using which the manipulations will be made * @param Crawler $crawler The crawler to be manipulated * @param null|string $fallbackBaseUrl See {@link AbstractBot::resolveRelativeUrls()} * @param null|int $lastStep One of the constants defined in {@link ManipulationStep}, e.g. * {@link ManipulationStep::REMOVE_ELEMENT_ATTRIBUTES}. If this is null, all the * manipulation steps will be applied. */ private function applyHtmlManipulationSteps($bot, &$crawler, $fallbackBaseUrl = null, $lastStep = null): void { $isForPost = $this->isManipulationOptionsForPost() ?: false; $keyFactory = $isForPost ? PostHtmlManipKeyFactory::getInstance() : CategoryHtmlManipKeyFactory::getInstance(); if ($this->getData()->get('fromRequestFilter') === '1') return; $provider = new PageFilterDependencyProvider($bot, null); $bot->applyFilterSetting($keyFactory->getRequestFiltersKey(), $provider); if ($lastStep === ManipulationStep::NONE) return; $manipulationOptions = $this->data->getManipulationOptions(); // Make initial replacements $crawler = $bot->makeInitialReplacements($crawler, Utils::array_get($manipulationOptions, $keyFactory->getFindReplaceFirstLoadKey()), $isForPost); $bot->setCrawler($crawler); if ($lastStep === ManipulationStep::INITIAL_REPLACEMENTS) return; // Apply HTML manipulations $bot->applyFindAndReplaceInElementAttributes($crawler, $keyFactory->getFindReplaceElementAttributesKey()); if ($lastStep === ManipulationStep::FIND_REPLACE_ELEMENT_ATTRIBUTES) return; $bot->applyExchangeElementAttributeValues($crawler, $keyFactory->getExchangeElementAttributesKey()); if ($lastStep === ManipulationStep::EXCHANGE_ELEMENT_ATTRIBUTES) return; $bot->applyRemoveElementAttributes($crawler, $keyFactory->getRemoveElementAttributesKey()); if ($lastStep === ManipulationStep::REMOVE_ELEMENT_ATTRIBUTES) return; $bot->applyFindAndReplaceInElementHTML($crawler, $keyFactory->getFindReplaceElementHtmlKey()); if ($lastStep === ManipulationStep::FIND_REPLACE_ELEMENT_HTML) return; $bot->applyConvertJsonToHtml($crawler, $keyFactory->getConvertJsonToHtmlKey()); $bot->applyConvertJsonToHtmlAuto($crawler, $keyFactory->getConvertJsonToHtmlAutoKey()); if ($lastStep === ManipulationStep::CONVERT_JSON_TO_HTML) return; // Clear the crawler from unnecessary elements $bot->removeElementsFromCrawler($crawler, Utils::array_get($manipulationOptions, $keyFactory->getUnnecessaryElementSelectorsKey())); if ($lastStep === ManipulationStep::REMOVE_ELEMENTS_FROM_CRAWLER) return; // Resolve relative URLs $bot->resolveRelativeUrls($crawler, $fallbackBaseUrl); if ($this->getData()->get('fromPageFilter') === '1') return; $bot->applyFilterSetting($keyFactory->getPageFiltersKey(), $provider); } /** * Calculates memory usage using {@link $memoryInitial} * @return string */ private function calculateMemoryUsage(): string { return number_format((memory_get_usage() - $this->memoryInitial) / 1000000, 2); } /** * Calculates elapsed time using {@link $startTime} * @return string */ private function calculateElapsedTime(): string { return number_format((microtime(true) - $this->startTime) * 1000, 2); } }