| Current Path : /home/digilove/public_html/administrator/components/com_jmap/models/ |
| Current File : /home/digilove/public_html/administrator/components/com_jmap/models/aigenerator.php |
<?php
// namespace administrator\components\com_jmap\models;
/**
*
* @package JMAP::AIGENERATOR::administrator::components::com_jmap
* @subpackage models
* @author Joomla! Extensions Store
* @copyright (C) 2021 - Joomla! Extensions Store
* @license GNU/GPLv2 http://www.gnu.org/licenses/gpl-2.0.html
*/
defined ( '_JEXEC' ) or die ( 'Restricted access' );
/**
* Google model responsibilities for access Google Analytics and Webmasters Tools API
*
* @package JMAP::AIGENERATOR::administrator::components::com_jmap
* @subpackage models
* @since 3.1
*/
interface IModelAIGenerator {
/**
* Generate data method for the AI contents API
*
* @access public
* @param string $keywordPhrase
* @param string $selectedApi
* @return mixed string
*/
public function generateAIContentData($keywordPhrase, $selectedApi);
}
/**
* Sources model concrete implementation <<testable_behavior>>
*
* @package JMAP::GOOGLE::administrator::components::com_jmap
* @subpackage models
* @since 3.1
*/
class JMapModelAIGenerator extends JMapModel implements IModelAIGenerator {
/**
* Google_Client object
*
* @access private
* @var Google_Client
*/
private $client;
/**
* Build list entities query
*
* @access protected
* @return string
*/
protected function buildListQuery() {
// WHERE
$where = array ();
$whereString = null;
$orderString = null;
// TEXT FILTER
if ($this->state->get ( 'searchword' )) {
$where [] = "(s.keywords_phrase LIKE " . $this->_db->quote("%" . $this->state->get ( 'searchword' ) . "%") . ")";
}
// LANGUAGE FILTER
$language = $this->state->get ( 'language' );
if ($language && $language != '*' && $this->getLanguagePluginEnabled()) {
$where [] = "(s.language = " . $this->_db->quote($this->state->get ( 'language' )) . ")";
}
// API FILTER
$contentsApi = $this->state->get ( 'contentsapi' );
if ($contentsApi) {
$where [] = "(s.api = " . $this->_db->quote($this->state->get ( 'contentsapi' )) . ")";
}
if (count ( $where )) {
$whereString = "\n WHERE " . implode ( "\n AND ", $where );
}
// ORDERBY
if ($this->state->get ( 'order' )) {
$orderString = "\n ORDER BY " . $this->state->get ( 'order' ) . " ";
}
// ORDERDIR
if ($this->state->get ( 'order_dir' )) {
$orderString .= $this->state->get ( 'order_dir' );
}
$query = "SELECT s.*, u.name AS editor" .
"\n FROM #__jmap_aigenerator AS s" .
"\n LEFT JOIN #__users AS u" .
"\n ON s.checked_out = u.id" .
$whereString .
$orderString;
return $query;
}
/**
* Function to purify common contents pitfalls and contents
*
* @acceess private
* @param string $contents
* @return string
*/
private function purifyContents($contents) {
// Decode all HTML entities to UTF-8
$contents = html_entity_decode($contents, ENT_QUOTES, 'UTF-8');
// Convert all UTF-8 unicode characters to symbols
$contents = preg_replace_callback('/\\\\u([0-9a-fA-F]{4})/imu', function ($match) {
return mb_convert_encoding(pack('H*', $match[1]), 'UTF-8', 'UCS-2BE');
}, $contents);
// Remove carriage returns
$contents = preg_replace( "/\r|\n|\\\\n|src=\"\"/imu", "", $contents );
// Remove escaped slashes
$contents = preg_replace('#\\\/#imu', '/', $contents );
// Rmove <noscript> tags
$contents = preg_replace( "/<noscript>(.*)<\/noscript>/imuU", "", $contents );
// Remove all links referencing original contents, keep inner text or remove unclosed tags
if($this->componentParams->get('aigenerator_remove_links', 1)) {
$contents = preg_replace('#(<a.*?>)(.*?)(</a>)#imu', '$2', $contents);
$contents = preg_replace('#(<a.*?>)#imu', '', $contents);
$contents = preg_replace('#(<button.*?>)(.*?)(</button>)#imu', '', $contents);
}
// Images should be removed?
if($this->getState('removeimgs')) {
$contents = preg_replace('#(<img.*?\/?>)#i', '', $contents);
}
// Replace headers with plain paragraphs
$contents = preg_replace('/<h\d.*>/imuU', '<p>', $contents);
$contents = preg_replace('/<\/h\d>/imu', '</p>', $contents);
// Reduce extra spaces and normalize them
$contents = preg_replace('/\s{2,}/imu', ' ', $contents);
$contents = preg_replace('/\t{2,}/imu', ' ', $contents);
// Remove onevents
$contents = preg_replace( "/oncontextmenu=\"|onmousedown=\"|onload=\"|onclick=\"|onerror=\"|onmouseover=\"|onmouseout=\"/im", "data-onevent=\"", $contents );
// Replace lazy loaded sources with standard ones
if($this->componentParams->get('aigenerator_turn_datasrc', 1)) {
$contents = preg_replace( '/src=\s*["\']([^"]*)(data:image)/imu', 'data-lazyoldsrc="$1$2', $contents );
$contents = preg_replace( "/data-src/imu", "src", $contents );
$contents = preg_replace( "/data-original/imu", "src", $contents );
$contents = preg_replace( "/data-lazyload/imu", "src", $contents );
$contents = preg_replace( "/data-dt-lazy-src/imu", "src", $contents );
$contents = preg_replace( "/data-lazy-src/imu", "src", $contents );
if($this->componentParams->get('aigenerator_remove_srcset', 1)) {
$contents = preg_replace( '/srcset=\s*(["\'])/imu', 'data-noloadsrcset=$1', $contents );
}
}
return $contents;
}
/**
* Get the state of the language filter plugin and Joomla multilanguage
*
* @access public
* @return bool
*/
public function getLanguagePluginEnabled() {
$languageFilterPluginEnabled = false;
// Check if multilanguage dropdown is always active
if($this->getComponentParams()->get('showalways_language_dropdown', false)) {
$languageFilterPluginEnabled = true;
} else {
// Detect Joomla Language Filter plugin enabled
$query = "SELECT " . $this->_db->quoteName('enabled') .
"\n FROM #__extensions" .
"\n WHERE " . $this->_db->quoteName('element') . " = " . $this->_db->quote('languagefilter') .
"\n OR " . $this->_db->quoteName('element') . " = " . $this->_db->quote('jfdatabase');
$this->_db->setQuery($query);
$languageFilterPluginEnabled = $this->_db->loadResult();
}
return $languageFilterPluginEnabled;
}
/**
* Return select lists used as filter for listEntities
*
* @access public
* @return array
*/
public function getFilters() {
$filters = [];
$languageOptions = JMapHtmlLanguages::getAvailableLanguageOptions(true);
$filters ['languages'] = JHtml::_ ( 'select.genericlist', $languageOptions, 'language', 'onchange="Joomla.submitform();" class="form-select hidden-phone"', 'value', 'text', $this->getState ( 'language' ) );
// Selection of the AI API
$chosenGeneratorAPI = array();
$chosenGeneratorAPI[] = JHtml::_('select.option', '', JText::_('COM_JMAP_AIGENERATOR_ALL_API'));
$chosenGeneratorAPI[] = JHtml::_('select.option', 'google', JText::_('COM_JMAP_AIGENERATOR_GOOGLE_API'));
$chosenGeneratorAPI[] = JHtml::_('select.option', 'bing', JText::_('COM_JMAP_AIGENERATOR_BING_API'));
$chosenGeneratorAPI[] = JHtml::_('select.option', 'openai', JText::_('COM_JMAP_AIGENERATOR_OPENAI_API'));
$filters ['contentsapi'] = JHtml::_ ( 'select.genericlist', $chosenGeneratorAPI, 'contentsapi', 'onchange="Joomla.submitform();" class="form-select hidden-phone"', 'value', 'text', $this->getState ( 'contentsapi' ) );
return $filters;
}
/**
* Return select lists used as filter for editEntity
*
* @access public
* @param Object $record
* @return array
*/
public function getLists($record = null) {
$lists = [];
// Selection of the AI API
$chosenGeneratorAPI = array();
$chosenGeneratorAPI[] = JHtml::_('select.option', 'google', JText::_('COM_JMAP_AIGENERATOR_GOOGLE_API'));
$chosenGeneratorAPI[] = JHtml::_('select.option', 'bing', JText::_('COM_JMAP_AIGENERATOR_BING_API'));
$chosenGeneratorAPI[] = JHtml::_('select.option', 'openai', JText::_('COM_JMAP_AIGENERATOR_OPENAI_API'));
$lists ['ai_generator_contents_api'] = JHtml::_( 'select.radiolist', $chosenGeneratorAPI, 'api', '', 'value', 'text', $record->api, 'api_');
// Priority select list
$options = array ();
$options [] = JHtml::_ ( 'select.option', null, JText::_ ( 'COM_JMAP_SELECT_MAX_AI_RESULTS' ) );
$arrayMaxResults = array (
'1' => JText::_('COM_JMAP_SELECT_MAX_AI_RESULTS_1_ITEMS'),
'2' => JText::_('COM_JMAP_SELECT_MAX_AI_RESULTS_2_ITEMS'),
'3' => JText::_('COM_JMAP_SELECT_MAX_AI_RESULTS_3_ITEMS'),
'4' => JText::_('COM_JMAP_SELECT_MAX_AI_RESULTS_4_ITEMS'),
'5' => JText::_('COM_JMAP_SELECT_MAX_AI_RESULTS_5_ITEMS'),
'6' => JText::_('COM_JMAP_SELECT_MAX_AI_RESULTS_6_ITEMS'),
'7' => JText::_('COM_JMAP_SELECT_MAX_AI_RESULTS_7_ITEMS'),
'8' => JText::_('COM_JMAP_SELECT_MAX_AI_RESULTS_8_ITEMS'),
'9' => JText::_('COM_JMAP_SELECT_MAX_AI_RESULTS_9_ITEMS'),
'10' => JText::_('COM_JMAP_SELECT_MAX_AI_RESULTS_10_ITEMS'),
'15' => JText::_('COM_JMAP_SELECT_MAX_AI_RESULTS_15_ITEMS'),
'20' => JText::_('COM_JMAP_SELECT_MAX_AI_RESULTS_20_ITEMS'),
'25' => JText::_('COM_JMAP_SELECT_MAX_AI_RESULTS_25_ITEMS'),
'30' => JText::_('COM_JMAP_SELECT_MAX_AI_RESULTS_30_ITEMS'),
'35' => JText::_('COM_JMAP_SELECT_MAX_AI_RESULTS_35_ITEMS'),
'40' => JText::_('COM_JMAP_SELECT_MAX_AI_RESULTS_40_ITEMS'),
'45' => JText::_('COM_JMAP_SELECT_MAX_AI_RESULTS_45_ITEMS'),
'50' => JText::_('COM_JMAP_SELECT_MAX_AI_RESULTS_50_ITEMS')
);
foreach ( $arrayMaxResults as $value => $text ) {
$options [] = JHtml::_ ( 'select.option', $value, $text );
}
$lists ['ai_generator_max_results'] = JHtml::_ ( 'select.genericlist', $options, 'maxresults', 'class="form-select"', 'value', 'text', $record->maxresults );
// Temperature list
$options = array ();
$options [] = JHtml::_ ( 'select.option', null, JText::_ ( 'COM_JMAP_AI_TEMPERATURE_SELECT_TEMPERATURE' ) );
$arrayTemperature = array(
'0.1' => JText::_('COM_JMAP_AI_TEMPERATURE_01'),
'0.2' => JText::_('COM_JMAP_AI_TEMPERATURE_02'),
'0.4' => JText::_('COM_JMAP_AI_TEMPERATURE_04'),
'0.5' => JText::_('COM_JMAP_AI_TEMPERATURE_05'),
'0.6' => JText::_('COM_JMAP_AI_TEMPERATURE_06'),
'0.8' => JText::_('COM_JMAP_AI_TEMPERATURE_08'),
'1.0' => JText::_('COM_JMAP_AI_TEMPERATURE_10'),
'1.2' => JText::_('COM_JMAP_AI_TEMPERATURE_12'),
'1.4' => JText::_('COM_JMAP_AI_TEMPERATURE_14'),
'1.6' => JText::_('COM_JMAP_AI_TEMPERATURE_16'),
'1.8' => JText::_('COM_JMAP_AI_TEMPERATURE_18'),
'2.0' => JText::_('COM_JMAP_AI_TEMPERATURE_20')
);
foreach ( $arrayTemperature as $value => $text ) {
$options [] = JHtml::_ ( 'select.option', $value, $text );
}
$temperature = $record->temperature ? $record->temperature : '0.5';
$lists ['temperature'] = JHtml::_ ( 'select.genericlist', $options, 'temperature', 'class="form-select"', 'value', 'text', $temperature );
// Language dropdown
$languageFilterPluginEnabled = $this->getLanguagePluginEnabled();
$languageOptions = JMapHtmlLanguages::getAvailableLanguageOptions(false, true);
if(count($languageOptions) >= 2 && $languageFilterPluginEnabled) {
$lists['languages'] = JMapHelpersHtml::genericlist( $languageOptions, 'language', 'class="form-select"', 'value', 'text', $record->language, 'aigenerator_language', 'languagecode' );
// Append a flag button image if the language is a specific one
if($record->language && $record->language != '*') {
$lists['languages'] .= '<img id="language_flag_image" src="' . JUri::root(false) . 'media/mod_languages/images/' . JString::str_ireplace('-', '_', $record->language) . '.gif" alt="language_flag" />';
}
}
return $lists;
}
/**
* Main get data methods
*
* @access public
* @return Object[]
*/
public function getData() {
// Build query
$query = $this->buildListQuery ();
$this->_db->setQuery ( $query, $this->getState ( 'limitstart' ), $this->getState ( 'limit' ) );
try {
$result = $this->_db->loadObjectList ();
if($this->_db->getErrorNum()) {
throw new JMapException(JText::_('COM_JMAP_ERROR_RETRIEVING_DATA') . $this->_db->getErrorMsg(), 'error');
}
} catch (JMapException $e) {
$this->app->enqueueMessage($e->getMessage(), $e->getErrorLevel());
$result = array();
} catch (Exception $e) {
$jmapException = new JMapException($e->getMessage(), 'error');
$this->app->enqueueMessage($jmapException->getMessage(), $jmapException->getErrorLevel());
$result = array();
}
return $result;
}
/**
* Delete entity
*
* @param array $ids
* @access public
* @return boolean
*/
public function deleteEntity($ids) {
$table = $this->getTable ();
// Ciclo su ogni entity da cancellare
if (is_array ( $ids ) && count ( $ids )) {
foreach ( $ids as $id ) {
try {
$table->load($id);
if($table->api == 'openai') {
// If AI model is ChatGPT, delete also local images files
$imagesFolder = md5($table->keywords_phrase);
$imagesPath = JPATH_ROOT . '/administrator/components/com_jmap/cache/chatgpt/' . $imagesFolder;
if(is_dir($imagesPath)) {
JFolder::delete($imagesPath);
}
}
if (! $table->delete ( $id )) {
throw new JMapException ( $table->getError (), 'error' );
}
} catch ( JMapException $e ) {
$this->setError ( $e );
return false;
} catch ( Exception $e ) {
$jmapException = new JMapException ( $e->getMessage (), 'error' );
$this->setError ( $jmapException );
return false;
}
}
}
return true;
}
/**
* Generate data method for the AI contents API
*
* @access public
* @param string $keywordPhrase
* @param string $selectedApi
* @return mixed Contents on success, false on failure exceptions
*/
public function generateAIContentData($keywordPhrase, $selectedApi) {
$cParams = $this->getComponentParams ();
// Hold the final concatenated contents to be returned and stored in the DB
$serviceAIContents = '';
// BING API endpoint
if($selectedApi == 'bing') {
$url = "https://www.bing.com/news/search?q=%s&setlang=%s&format=RSS";
} else {
$url = '';
}
// Format the API endpoint based on user's settings
$keywordPhraseEncoded = urlencode($keywordPhrase);
$apiUrl = sprintf($url, $keywordPhraseEncoded, $this->getState('contentlanguage'));
try {
// Fetch remote data to scrape
$httpTransport = $cParams->get ( 'aigenerator_service_http_transport', 'curl' ) == 'socket' ? 'file_get_contents' : new JMapHttpTransportCurl(true);
if (is_object ( $httpTransport )) {
$connectionAdapter = new JMapHttp ( $httpTransport, $cParams );
}
if($selectedApi == 'bing') {
// CURL lib
if (is_object ( $httpTransport )) {
// Init headers
$headers = array (
'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
'Accept-Language' => 'en,it;q=0.9,en-US;q=0.8,de;q=0.7,es;q=0.6,fr;q=0.5,ru;q=0.4,ja;q=0.3,el;q=0.2,sk;q=0.1,nl;q=0.1,ar;q=0.1,sv;q=0.1,da;q=0.1',
'Cache-Control' => 'no-cache',
'Connection' => 'keep-alive',
'Pragma' => 'no-cache',
'User-Agent' => 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.4664.93 Safari/537.36'
);
$httpResponse = $connectionAdapter->get ( $apiUrl, $headers );
} else {
// file_get_contents case
$opts = array (
'http' => array (
'method' => "GET",
'header' => "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\r\n" .
"Accept-Language: en,it;q=0.9,en-US;q=0.8,de;q=0.7,es;q=0.6,fr;q=0.5,ru;q=0.4,ja;q=0.3,el;q=0.2,sk;q=0.1,nl;q=0.1,ar;q=0.1,sv;q=0.1,da;q=0.1\r\n" .
"Cache-Control: no-cache\r\n" .
"Connection: keep-alive\r\n" .
"Pragma: no-cache\r\n" .
"User-Agent: Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.4664.93 Safari/537.36",
'timeout' => 5
)
);
$context = stream_context_create ( $opts );
$response = file_get_contents ( $apiUrl, false, $context );
if ($response) {
$httpResponse = new JMapHttpResponse();
$httpResponse->code = 200;
$httpResponse->body = $response;
} else {
throw new RuntimeException ( JText::sprintf ( 'COM_JMAP_AIGENERATOR_ERROR_GENERATING_CONTENT', 409 ) );
}
}
// Check if HTTP status code is 200 OK
if ($httpResponse->code != 200 || !$httpResponse->body) {
throw new RuntimeException ( JText::sprintf( 'COM_JMAP_AIGENERATOR_ERROR_GENERATING_CONTENT', $httpResponse->code) );
}
// Process result as XML document. Extract all single feed URLs for further AI scraping analyzing
libxml_use_internal_errors(true);
$objXmlDocument = simplexml_load_string($httpResponse->body);
if ($objXmlDocument === false) {
$errors = [];
foreach(libxml_get_errors() as $error) {
$errors[] = $error->message;
}
throw new RuntimeException ( JText::sprintf( 'COM_JMAP_AIGENERATOR_ERROR_XML', implode('. ', $errors)) );
}
$objJsonDocument = json_encode($objXmlDocument);
$feedArrayStructure = json_decode($objJsonDocument, true);
if(!is_array($feedArrayStructure)) {
throw new RuntimeException ( JText::sprintf( 'COM_JMAP_AIGENERATOR_ERROR_DATA' ));
}
} else {
// Do a Google SERP and build simulate an RSS feed from Google SERPs
$customHeaders = array (
'acceptlanguage' => $this->getState ( 'contentlanguage' ),
'cookie' => 'CGIC=InZ0ZXh0L2h0bWwsYXBwbGljYXRpb24veGh0bWwreG1sLGFwcGxpY2F0aW9uL3htbDtxPTAuOSxpbWFnZS93ZWJwLGltYWdlL2FwbmcsKi8qO3E9MC44LGFwcGxpY2F0aW9uL3NpZ25lZC1leGNoYW5nZTt2PWIz; CONSENT=YES+GB.en+20151207-13-0; SEARCH_SAMESITE=CgQI3o0B; SID=oQeF2oOrI8ct0mg68RmuhS9lq-rL5YDqzv_oWZFHyFm6SZYu_9MqGGFtgMqEco0A-5927g.; HSID=A6bVsYvjyB4aFQt1R; SSID=AMbs1MzN52UyDqZwm; APISID=iHy5G9KqldrHN2Nf/AQP6jZh1BhpuJLCqt; SAPISID=CkVydZgJs3KyBEJk/AEU5W6P4uiXHScqyS; ANID=AHWqTUmybJX0DetXtktJFaw06fqjq77B9CbX7QR6GkOKlYm1lB1MCUWoNbOE0Lu7; NID=188=FY6KW-ygeYgrkLl8FnyQBrChL9FP1UoS9WWs3IbJLqnuAmK6qATL-gwSNV6tSl4d7sgUU5By9tFM9zp1phivP9oykPRtPsqf4zH37OpRmjYxq6R0SSJr0Ez41qbw3HYW-uY0R_5sG-CE5jCEYWzEMU8Hb-emr51PGPeTKbeQQVOElFVjrVkJiPLQbNB8Sma6BEzzYwiFC6U-rXs1_Y42mcJEjTh31y40M00ffpGEXpTkmY1DJb4LKX6se0KnQk4YGBgzLoNhB5GyKv5YeL74yURM3linL_UeIqE3FskyQG1y; 1P_JAR=2019-9-20-15; DV=k4rz414DoMFIgAbC04tAEYubN-jz1JZw4P5MyRCtQwAAACD7P2SWOiEwcAAAAHC4M1I0Yx1kMQAAAA; SIDCC=AN0-TYvfXZ1gZPUm4szeckxeJXAciDzQvACQp6tDglk9LWV4SqY3nvNYozuIr2DLAn8IwgI8fvs'
);
$feedArrayStructure = [ ];
$feedArrayStructure ['channel'] = [ ];
$feedArrayStructure ['channel'] ['item'] = [ ];
$maxSerpsResults = $this->getState ( 'maxresults' );
if ($maxSerpsResults >= 1 && $maxSerpsResults <= 10) {
$maxPages = 2;
} elseif ($maxSerpsResults >= 11 && $maxSerpsResults <= 20) {
$maxPages = 3;
} elseif ($maxSerpsResults >= 21 && $maxSerpsResults <= 30) {
$maxPages = 4;
} elseif ($maxSerpsResults >= 31 && $maxSerpsResults <= 40) {
$maxPages = 5;
} elseif ($maxSerpsResults >= 41 && $maxSerpsResults <= 50) {
$maxPages = 6;
}
$startPageCounter = 1;
for($p = 0; $p < $maxPages; $p ++) {
$results = JMapSeostatsServicesGoogle::getSerps ( $keywordPhrase, $startPageCounter * 10, $customHeaders );
if ($results) {
foreach ( $results as $page ) {
foreach ( $page as $pageSerp ) {
$feedArrayStructure ['channel'] ['item'] [] = $pageSerp ['url'];
}
}
}
$startPageCounter++;
}
}
if(!isset($feedArrayStructure['channel']['item'])) {
throw new RuntimeException ( JText::sprintf( 'COM_JMAP_AIGENERATOR_ERROR_DATA_ITEM' ));
}
if(count($feedArrayStructure['channel']['item']) == 0) {
throw new RuntimeException ( JText::sprintf( 'COM_JMAP_AIGENERATOR_ERROR_DATA_EMPTY_ITEM' ));
}
// Start crawling and elaborating remote resources
$counter = 0;
$limitSnippets = $this->getState('maxresults');
foreach ($feedArrayStructure['channel']['item'] as $itemLinkToAnalyze) {
// Hold the response for a given page
$serviceAIResponse = '';
$queryVars = [];
// CURL lib
if (is_object ( $httpTransport )) {
// Parse the full URL
if($selectedApi == 'bing') {
$parsedUrl = parse_url($itemLinkToAnalyze['link']);
parse_str($parsedUrl['query'], $queryVars);
} else {
$queryVars['url'] = $itemLinkToAnalyze;
}
$httpResponse = $connectionAdapter->get ( $queryVars['url'] );
// Check if HTTP status code is 200 OK
if ($httpResponse->code != 200 || !$httpResponse->body) {
continue;
}
// Set the response contents
$serviceAIResponse = $httpResponse->body;
// Put the case to lower
$httpResponse->headers = array_change_key_case($httpResponse->headers, CASE_LOWER);
// Detect if contents are utf8 encoded otherwise convert them
if(isset($httpResponse->headers['content-type']) && !preg_match('/utf-8/i', $httpResponse->headers['content-type'])) {
/**
* Note: PHP Readability expects UTF-8 encoded content.
* If your content is not UTF-8 encoded, convert it
* first before passing it to PHP Readability.
*/
if(preg_match('/ISO-8859-1/i', $httpResponse->headers['content-type'])) {
$fromEncoding = 'ISO-8859-1';
} else {
$fromEncoding = mb_detect_encoding($serviceAIResponse);
}
if($fromEncoding) {
$serviceAIResponse = mb_convert_encoding($serviceAIResponse, 'UTF-8', $fromEncoding);
} else {
continue;
}
}
} else {
$queryVars['url'] = $itemLinkToAnalyze['link'];
$serviceAIResponse = file_get_contents ( $itemLinkToAnalyze['link'], false, $context );
if (!$serviceAIResponse) {
continue;
}
}
/**
* If we've got Tidy, let's clean up input.
* This step is highly recommended - PHP's default HTML parser
* often doesn't do a great job and results in strange output.
*/
if (function_exists('tidy_parse_string')) {
$tidy = tidy_parse_string($serviceAIResponse, array(), 'UTF8');
$tidy->cleanRepair();
$serviceAIResponse = $tidy->value;
}
$readability = new JMapAigeneratorReadability($serviceAIResponse, $queryVars['url']);
$result = $readability->init();
$readability->removeScripts($readability->getContent());
if ($result) {
$readabilityTitle = JString::ucfirst($readability->getTitle()->textContent);
$readabilityContents = $readability->getContent()->innerHTML;
// If we've got Tidy, let's clean it up for output
if (function_exists('tidy_parse_string')) {
$tidy = tidy_parse_string($readabilityContents, array('indent'=>true, 'show-body-only' => true), 'UTF8');
$tidy->cleanRepair();
$serviceAIContents .= '{title}' . $readabilityTitle . '{/title}{content}' . $tidy->value . '{/content}{contentdivider}';
} else {
$readabilityContents = JMapAigeneratorHtmlawed::executeHtmlLawed($readabilityContents);
$serviceAIContents .= '{title}' . $readabilityTitle . '{/title}{content}' . $readabilityContents . '{/content}{contentdivider}';
}
}
// Respect pagination max snippets limit
if($counter == ($limitSnippets - 1)) {
break;
}
$counter++;
}
} catch ( RuntimeException $e ) {
$jmapException = new JMapException ( $e->getMessage (), 'error' );
$this->setError ( $jmapException );
return false;
} catch ( Exception $e ) {
$jmapException = new JMapException ( $e->getMessage (), 'error' );
$this->setError ( $jmapException );
return false;
}
// Clean the content from common pitfalls
$serviceAIContents = $this->purifyContents($serviceAIContents);
return $serviceAIContents;
}
/**
* Generate data method for the AI contents API
*
* @access public
* @param string $keywordPhrase
* @param string $onlyPredicted
* @return mixed Contents on success, false on failure exceptions
*/
public function generateOpenAIContentData($keywordPhrase, $onlyPredicted = false) {
$cParams = $this->getComponentParams ();
// Hold the final concatenated contents to be returned and stored in the DB
$serviceAIContents = '';
// ChatGPT API
$chatgptApi = $cParams->get('chatgpt_api', 'completions');
$chatgptModel = $cParams->get('chatgpt_api_model', 'gpt-3.5-turbo');
if($chatgptApi == 'completions') {
$apiUrl = "https://api.openai.com/v1/completions";
} else {
$apiUrl = 'https://api.openai.com/v1/chat/completions';
}
$apiImageUrl = "https://api.openai.com/v1/images/generations";
$removeImages = $this->getState('removeimgs');
$openAiApiKey = trim($cParams->get('chatgpt_apikey', ''));
$numResults = (int)$this->getState('maxresults');
$temperature = (float)$this->getState('temperature');
$maxTokens = (int)$cParams->get('chatgpt_maxtokens', 2048);
$imageSize = $cParams->get('chatgpt_images_size', '256x256');
try {
// The ApiKey is required
if(!$openAiApiKey) {
throw new Exception ( JText::_( 'COM_JMAP_AIGENERATOR_CHATGPT_MISSING_APIKEY') );
}
// Fetch remote data to scrape
$httpTransport = $cParams->get ( 'aigenerator_service_http_transport', 'curl' ) == 'socket' ? 'file_get_contents' : new JMapHttpTransportCurl (true);
// CURL lib
if (is_object ( $httpTransport )) {
$connectionAdapter = new JMapHttp ( $httpTransport, $cParams );
// Init headers
$headers = array (
'Content-Type' => 'application/json',
'Authorization' => 'Bearer ' . $openAiApiKey
);
// Open AI text post params
if($chatgptApi == 'completions') {
$postData = [
'max_tokens' => $maxTokens,
'model' => 'gpt-3.5-turbo-instruct',
'n' => $numResults,
'temperature' => $temperature,
'prompt' => strip_tags ( $keywordPhrase )
];
} else {
$postData = [
'max_tokens' => $maxTokens,
'model' => $chatgptModel,
'n' => $numResults,
'temperature' => $temperature,
'messages' => [
[
'role' => 'user',
'content' => strip_tags ( $keywordPhrase )
]
]
];
}
$httpResponse = $connectionAdapter->post ( $apiUrl, json_encode($postData), $headers );
// Open AI images post params
if(!$removeImages && $httpResponse->code == 200) {
$postDataImages = [ 'n' => $numResults,
'size' => $imageSize,
'prompt' => strip_tags($keywordPhrase)
];
$httpResponseImages = $connectionAdapter->post ($apiImageUrl, json_encode($postDataImages), $headers);
}
} else {
// file_get_contents case
// Open AI post params
if($chatgptApi == 'completions') {
$postData = json_encode ( [
'max_tokens' => $maxTokens,
'model' => 'gpt-3.5-turbo-instruct',
'n' => $numResults,
'temperature' => $temperature,
'prompt' => strip_tags ( $keywordPhrase )
] );
} else {
$postData = json_encode ( [
'max_tokens' => $maxTokens,
'model' => $chatgptModel,
'n' => $numResults,
'temperature' => $temperature,
'messages' => [
[
'role' => 'user',
'content' => strip_tags ( $keywordPhrase )
]
]
] );
}
$opts = array (
'http' => array (
'method' => "POST",
'header' => "Content-Type: application/json\r\n" .
"Authorization: Bearer $openAiApiKey\r\n",
'content' => $postData,
'timeout' => 20
)
);
$context = stream_context_create ( $opts );
$response = file_get_contents ( $apiUrl, false, $context );
if ($response) {
$httpResponse = new JMapHttpResponse ();
$httpResponse->code = 200;
$httpResponse->body = $response;
if(!$removeImages) {
// Open AI images post params
$postDataImages = json_encode( [
'n' => $numResults,
'size' => $imageSize,
'prompt' => strip_tags ( $keywordPhrase )
] );
$opts = array (
'http' => array (
'method' => "POST",
'header' => "Content-Type: application/json\r\n" .
"Authorization: Bearer $openAiApiKey\r\n",
'content' => $postDataImages,
'timeout' => 20
)
);
$context = stream_context_create ( $opts );
$responseImages = file_get_contents ( $apiImageUrl, false, $context );
if($responseImages) {
$httpResponseImages = new JMapHttpResponse ();
$httpResponseImages->code = 200;
$httpResponseImages->body = $responseImages;
} else {
throw new RuntimeException ( JText::sprintf ( 'COM_JMAP_AIGENERATOR_ERROR_GENERATING_CONTENT', 409 ) );
}
}
} else {
throw new RuntimeException ( JText::sprintf ( 'COM_JMAP_AIGENERATOR_ERROR_GENERATING_CONTENT', 409 ) );
}
}
// Check if HTTP status code is 200 OK
if ($httpResponse->code != 200 || !$httpResponse->body) {
$errorInfo = $httpResponse->code;
if($httpResponse->body) {
$responseError = json_decode($httpResponse->body)->error->message;
$errorInfo = $errorInfo . ' - ' . $responseError;
}
throw new RuntimeException ( JText::sprintf( 'COM_JMAP_AIGENERATOR_ERROR_GENERATING_CONTENT', $errorInfo) );
}
// Success response to parse
$responseChatGPT = json_decode($httpResponse->body);
// Check if the HTTP status code for images is 200 OK
if(!$removeImages) {
$imagesFolder = md5($keywordPhrase);
$imagesPath = JPATH_ROOT . '/administrator/components/com_jmap/cache/chatgpt/' . $imagesFolder;
if(!is_dir($imagesPath)) {
JFolder::create($imagesPath);
}
$responseChatGPTImages = [];
if ($httpResponseImages->code == 200 && $httpResponseImages->body) {
$responseChatGPTImages = json_decode($httpResponseImages->body)->data;
}
}
$choices = $responseChatGPT->choices;
foreach ( $choices as $index => $chatChoice ) {
if (! $removeImages && array_key_exists ( $index, $responseChatGPTImages )) {
// Fetch of the remote image and local storing
$imgTag = '';
$bin = file_get_contents ( $responseChatGPTImages [$index]->url );
$nameChunks = explode ( '?', $responseChatGPTImages [$index]->url );
$imgUrl = $nameChunks [0];
$urlChunks = explode ( '/', $imgUrl );
$fileName = array_pop ( $urlChunks );
file_put_contents ( JPATH_ROOT . '/administrator/components/com_jmap/cache/chatgpt/' . $imagesFolder . '/' . $fileName, $bin );
$imgTag = '<img src="' . JUri::root ( false ) . 'administrator/components/com_jmap/cache/chatgpt/' . $imagesFolder . '/' . $fileName . '"/>';
if($chatgptApi == 'completions') {
$chatChoice->text = '<div class="chatgpt-snippet-image">' . $imgTag . '</div>' . $chatChoice->text;
} else {
$chatChoice->text = '<div class="chatgpt-snippet-image">' . $imgTag . '</div>' . $chatChoice->message->content;
}
} else {
if($chatgptApi == 'completions') {
$chatChoice->text = $chatChoice->text;
} else {
$chatChoice->text = $chatChoice->message->content;
}
}
$serviceAIContents .= '{title}' . JString::ucfirst ( $keywordPhrase ) . ' #' . ($index + 1) . '{/title} {content}' . $chatChoice->text . '{/content}{contentdivider}';
}
} catch ( RuntimeException $e ) {
$jmapException = new JMapException ( $e->getMessage (), 'error' );
$this->setError ( $jmapException );
return false;
} catch ( Exception $e ) {
$jmapException = new JMapException ( $e->getMessage (), 'error' );
$this->setError ( $jmapException );
return false;
}
// Return only the simple ChatGPT response without any addition or alteration
if($onlyPredicted) {
return $chatChoice->text;
}
// Clean the content from common pitfalls
$serviceAIContents = $this->purifyContents($serviceAIContents);
return $serviceAIContents;
}
}