Commit inicial - WordPress Análisis de Precios Unitarios

- WordPress core y plugins
- Tema Twenty Twenty-Four configurado
- Plugin allow-unfiltered-html.php simplificado
- .gitignore configurado para excluir wp-config.php y uploads

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2025-11-03 21:04:30 -06:00
commit a22573bf0b
24068 changed files with 4993111 additions and 0 deletions

View File

@@ -0,0 +1,421 @@
<?php
namespace WPDRMS\ASP\Media;
/* Prevent direct access */
use Exception;
use WPDRMS\ASP\Media\RemoteService\License;
use WPDRMS\ASP\Utils\FileManager;
use WPDRMS\ASP\Utils\Str;
defined( 'ABSPATH' ) or die( "You can't access this file directly." );
class Parser {
/**
* Mime groups array
*
* @var array
*/
private static $mimes = array(
'image' => array(
'image/jpeg',
'image/gif',
'image/png',
'image/bmp',
'image/tiff',
'image/x-icon',
),
'pdf' => array(
'application/pdf',
),
'text' => array(
'text/plain',
'text/csv',
'text/tab-separated-values',
'text/calendar',
'text/css',
'text/vtt',
'text/html',
),
'richtext' => array(
'text/richtext',
'application/rtf',
),
'mso_word' => array(
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.ms-word.document.macroEnabled.12',
'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
'application/vnd.ms-word.template.macroEnabled.12',
'application/vnd.oasis.opendocument.text',
),
'mso_excel' => array(
'application/vnd.ms-excel',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'application/vnd.ms-excel.sheet.macroEnabled.12',
'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
'application/vnd.ms-excel.template.macroEnabled.12',
'application/vnd.ms-excel.addin.macroEnabled.12',
'application/vnd.oasis.opendocument.spreadsheet',
'application/vnd.oasis.opendocument.chart',
'application/vnd.oasis.opendocument.database',
'application/vnd.oasis.opendocument.formula',
),
'mso_powerpoint' => array(
'application/vnd.ms-powerpoint',
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'application/vnd.ms-powerpoint.presentation.macroEnabled.12',
'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
'application/vnd.ms-powerpoint.slideshow.macroEnabled.12',
'application/vnd.openxmlformats-officedocument.presentationml.template',
'application/vnd.ms-powerpoint.template.macroEnabled.12',
'application/vnd.ms-powerpoint.addin.macroEnabled.12',
'application/vnd.openxmlformats-officedocument.presentationml.slide',
'application/vnd.ms-powerpoint.slide.macroEnabled.12',
'application/vnd.oasis.opendocument.presentation',
'application/vnd.oasis.opendocument.graphics',
),
);
private $args, $post, $filepath;
/**
* Constructor.
*
* @param \WP_Post $post
* @param array $args
*/
function __construct( $post, $args ) {
$defaults = array(
'media_service_send_file' => false,
'pdf_parser' => 'auto', // auto, smalot, pdf2txt
'text_content' => true,
'richtext_content' => true,
'pdf_content' => true,
'msword_content' => true,
'msexcel_content' => true,
'msppt_content' => true,
);
$this->post = $post;
$this->args = wp_parse_args($args, $defaults);
$this->args = apply_filters('asp_media_parser_args', $this->args, $defaults);
// To allow adding mimes programmatically
self::$mimes = apply_filters('asp/media/parser/mimes', self::$mimes);
}
/**
* Parses the file contents
*
* @return string|\WP_Error
*/
function parse( $remote = true ) {
$this->filepath = get_attached_file( $this->post->ID );
if ( is_wp_error($this->filepath) || empty($this->filepath) || !file_exists($this->filepath) ) {
return '';
}
$text = '';
if ( $this->isThisParseEnabled() ) {
if ( $remote ) {
$text = $this->parseRemote();
} else {
$text = $this->parseLocal();
}
/**
* In case of PDF there are many cases of gibberish in text when using OCR, such as
* "my text a<bc my other text" -> where "<bc" is treated as an opening HTML tag.
* Then running strip_tags("my text a<bc my other text") = "my text a" -> will remove everything
* after the tag starting bracket, because the "my other text" is considered as tag attributes.
* The best course of action here is to simply remove these symbols.
*/
if ( !is_wp_error($text) && $this->isThis('pdf') ) {
$text = str_replace(array( '<', '>' ), ' ', $text);
}
}
return $text;
}
function parseRemote() {
$text = '';
$license = License::getInstance();
if ( $license->active() && $license->valid() ) {
if ( !in_array('_asp_attachment_text', get_post_custom_keys($this->post->ID)) ) {
$license_data = $license->getData();
$license_max_filesize =
isset($license_data['stats'], $license_data['stats']['max_filesize']) ?
$license_data['stats']['max_filesize'] : 0;
$parser = new RemoteService\Parser(
$this->filepath,
wp_get_attachment_url($this->post->ID),
$license->get(),
$this->args['media_service_send_file'],
$license_max_filesize
);
$text = $parser->request();
$license->setStats($parser->getStats());
if ( !is_wp_error($text) ) {
update_post_meta($this->post->ID, '_asp_attachment_text', $text);
} else {
return $text;
}
} else {
$text = get_post_meta($this->post->ID, '_asp_attachment_text', true);
}
}
return $text;
}
function parseLocal() {
if ( $this->isThis('text') && $this->args['text_content'] ) {
$text = $this->parseTXT();
} elseif ( $this->isThis('richtext') && $this->args['richtext_content'] ) {
$text = $this->parseRTF();
} elseif ( $this->isThis('pdf') && $this->args['pdf_content'] ) {
$text = $this->parsePDF();
} elseif ( $this->isThis('mso_word') && $this->args['msword_content'] ) {
$text = $this->parseMSOWord();
} elseif ( $this->isThis('mso_excel') && $this->args['msexcel_content'] ) {
$text = $this->parseMSOExcel();
} elseif ( $this->isThis('mso_powerpoint') && $this->args['msppt_content'] ) {
$text = $this->parseMSOPpt();
} else {
$text = '';
}
return $text;
}
/**
* Checks if a mime type belongs to a certain mime group (text, richtext etc..)
*
* @param string $type
* @return bool
*/
public function isThis( string $type = 'text' ): bool {
return ( isset(self::$mimes[ $type ]) && in_array($this->post->post_mime_type, self::$mimes[ $type ]) );
}
public function isThisParseEnabled(): bool {
return ( $this->isThis('text') && $this->args['text_content'] ) ||
( $this->isThis('richtext') && $this->args['richtext_content'] ) ||
( $this->isThis('pdf') && $this->args['pdf_content'] ) ||
( $this->isThis('mso_word') && $this->args['msword_content'] ) ||
( $this->isThis('mso_excel') && $this->args['msexcel_content'] ) ||
( $this->isThis('mso_powerpoint') && $this->args['msppt_content'] ) ||
( $this->isThis('image') && License::instance()->active() && !License::instance()->isFree() );
}
/**
* Gets contents from a text based file
*
* @return string
*/
function parseTXT() {
$contents = FileManager::instance()->read($this->filepath);
// CSV files often store the values in quotes. We don't need those in this case.
if ( $this->post->post_mime_type == 'text/csv' ) {
$contents = str_replace(array( '"', "'" ), ' ', $contents);
}
return $contents;
}
/**
* Gets contents from a richtext file
*
* @return string
*/
function parseRTF() {
$rtf = FileManager::instance()->read($this->filepath);
if ( $rtf != '' ) {
include_once ASP_EXTERNALS_PATH . 'class.rtf-html-php.php';
$reader = new \ASP_RtfReader();
$reader->Parse($rtf);
$formatter = new \ASP_RtfHtml();
return html_entity_decode(strip_tags($formatter->Format($reader->root)));
}
return '';
}
/**
* Gets contents from a PDF file
*
* @return string
*/
function parsePDF() {
$args = $this->args;
$contents = '';
// PDF Parser for php 5.3 and above
if ( $args['pdf_parser'] == 'auto' || $args['pdf_parser'] == 'smalot' ) {
if ( version_compare(PHP_VERSION, '5.3', '>=') ) {
include_once ASP_EXTERNALS_PATH . 'class.pdfsmalot.php';
$parser = new \ASP_PDFSmalot();
$parser = $parser->getObj();
try {
$pdf = $parser->parseFile($this->filepath);
$contents = $pdf->getText();
} catch ( Exception $e ) {
echo 'Caught exception: ', $e->getMessage(), "\n";
}
}
}
// Different method maybe?
if ( $args['pdf_parser'] == 'auto' || $args['pdf_parser'] == 'pdf2txt' ) {
if ( $contents == '' ) {
include_once ASP_EXTERNALS_PATH . 'class.pdf2txt.php';
$pdfParser = new \ASP_PDF2Text();
$pdfParser->setFilename($this->filepath);
$pdfParser->decodePDF();
$contents = $pdfParser->output();
}
}
return $contents;
}
/**
* Gets contents from an Office Word file
*
* @return string
*/
function parseMSOWord() {
if ( false !== strpos( $this->post->post_mime_type, 'opendocument' ) ) {
$o = $this->getFileFromArchive('content.xml', $this->filepath);
} else {
$o = $this->getFileFromArchive('word/document.xml', $this->filepath);
}
return $o;
}
/**
* Gets contents from an Office Excel file
*
* @return string
*/
function parseMSOExcel() {
if ( false !== strpos($this->post->post_mime_type, 'opendocument') ) {
$o = $this->getFileFromArchive('content.xml', $this->filepath);
} elseif ( substr_compare($this->filepath, '.xls', -strlen('.xls')) === 0 ) {
$o = '';
include_once ASP_EXTERNALS_PATH . 'php-excel/autoload.php';
$reader = \Asan\PHPExcel\Excel::load(
$this->filepath,
function ( \Asan\PHPExcel\Reader\Xls $reader ) use ( $o ) {
$reader->ignoreEmptyRow(true);
}
);
foreach ( $reader->sheets() as $shk => $sheet ) {
$reader->setSheetIndex($shk);
foreach ( $reader as $row ) {
$o .= ' ' . Str::anyToString($row);
}
}
} else {
$o = $this->getFileFromArchive('xl/sharedStrings.xml', $this->filepath);
}
return $o;
}
/**
* Gets contents from an Office Powerpoint file
*
* @return string
*/
function parseMSOPpt() {
$out = '';
if ( class_exists( '\\ZipArchive' ) ) {
if ( false !== strpos($this->post->post_mime_type, 'opendocument') ) {
$out = $this->getFileFromArchive('content.xml', $this->filepath);
} else {
$zip = new \ZipArchive();
if ( true === $zip->open($this->filepath) ) {
$slide_num = 1;
while ( false !== ( $xml_index = $zip->locateName('ppt/slides/slide' . absint($slide_num) . '.xml') ) ) {
$xml = $zip->getFromIndex($xml_index);
$out .= ' ' . $this->getXMLContent($xml);
++$slide_num;
}
$zip->close();
} elseif ( substr_compare($this->filepath, '.ppt', -strlen('.ppt')) === 0 ) {
// This approach uses detection of the string "chr(0f).Hex_value.chr(0x00).chr(0x00).chr(0x00)" to find text strings, which are then terminated by another NUL chr(0x00). [1] Get text between delimiters [2]
$fileHandle = fopen($this->filepath, 'r');
$line = @fread($fileHandle, filesize($this->filepath));
$lines = explode(chr(0x0f), $line);
$outtext = '';
foreach ( $lines as $thisline ) {
if ( strpos($thisline, chr(0x00) . chr(0x00) . chr(0x00)) == 1 ) {
$text_line = substr($thisline, 4);
$end_pos = strpos($text_line, chr(0x00));
$text_line = substr($text_line, 0, $end_pos);
$text_line = preg_replace("/[^a-zA-Z0-9\s\,\.\-\n\r\t@\/\_\(\)]/", '', $text_line);
if ( strlen($text_line) > 1 ) {
$outtext .= substr($text_line, 0, $end_pos) . "\n";
}
}
}
$out = $outtext;
}
}
}
return $out;
}
/**
* Gets the content from an XML string
*
* @param $xml
* @return string
*/
private function getXMLContent( $xml ) {
if ( class_exists('\\DOMDocument') ) {
$dom = new \DOMDocument();
$dom->loadXML($xml, LIBXML_NOENT | LIBXML_XINCLUDE | LIBXML_NOERROR | LIBXML_NOWARNING);
return $dom->saveXML();
}
return '';
}
/**
* Gets a file from an archive, based on the xml file name
*
* @param $xml
* @param $filename
* @return string
*/
private function getFileFromArchive( $xml, $filename ) {
if ( class_exists('\\ZipArchive') && class_exists('\\DOMDocument') ) {
$output_text = '';
$zip = new \ZipArchive();
if ( true === $zip->open($filename) ) {
if ( false !== ( $xml_index = $zip->locateName($xml) ) ) {
$xml_data = $zip->getFromIndex($xml_index);
$dom = new \DOMDocument();
$dom->loadXML( $xml_data, LIBXML_NOENT | LIBXML_XINCLUDE | LIBXML_NOERROR | LIBXML_NOWARNING );
$output_text = $dom->saveXML();
}
$zip->close();
} else {
// File open error
return '';
}
return $output_text;
}
// The ZipArchive class is missing
return '';
}
}

View File

@@ -0,0 +1,186 @@
<?php
namespace WPDRMS\ASP\Media\RemoteService;
use WPDRMS\ASP\Patterns\SingletonTrait;
if ( !defined('ABSPATH') ) {
die('-1');
}
/**
* @phpstan-type LicenseStatsArr array{
* free: bool,
* max_files_usage: int,
* max_files: int|'unlimited',
* max_filesize: int
* }
* @phpstan-type LicenseDataArr array{
* license: string,
* active: bool,
* last_check: int,
* stats: LicenseStatsArr
* }
*/
class License {
use SingletonTrait;
/**
* @var LicenseDataArr
*/
private array $data;
private string $url = 'https://media1.ajaxsearchpro.com/';
private function __construct() {
$this->data = get_option(
'_asp_media_service_data',
array(
'license' => '',
'active' => false,
'stats' => array(
'free' => true,
'max_files_usage' => 1,
'max_files' => 0,
'max_filesize' => 0,
),
)
);
$this->refresh();
}
public function active(): bool {
return $this->data['active'] && $this->get() !== '';
}
public function isFree(): bool {
return $this->data['stats']['free'];
}
public function valid(): bool {
if ( $this->data['stats']['max_files'] === 'unlimited' ) {
return true;
}
if (
(int) $this->data['stats']['max_files_usage'] >= (int) $this->data['stats']['max_files']
) {
/**
* The "stats" are updated ONLY during indexing. If the max_file threshold was met during a recent
* index, then max_files < max_files_usage forever, and this function would return "false" all the time.
* If the last check was performed over 5 minutes ago, the report "true" even if the files
* threshold was met, so a request will be made to the media server to verify that.
*/
if ( ( time() - (int) $this->data['last_check'] ) > 300 ) {
return true;
} else {
return false;
}
} else {
return true;
}
}
public function refresh(): void {
if ( $this->active() ) {
if ( ( time() - (int) $this->data['last_check'] ) > 300 ) {
$this->activate($this->data['license']);
}
}
}
/**
* @param string $license
* @return array{
* success: 1|0,
* text: string
* }
*/
public function activate( string $license ): array {
$success = 0;
if (
strlen($license) === 36 ||
preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/', $license) === 1
) {
$response = wp_safe_remote_post(
$this->url,
array(
'body' => array(
'license' => $license,
),
)
);
if ( !is_wp_error($response) ) {
$data = json_decode($response['body'], true); // @phpstan-ignore-line
if ( !$data['success'] ) {
$text = $data['text'];
} else {
$this->set($license, true, $data['stats']);
$success = 1;
$text = 'License successfully activated!';
}
} else {
$text = $response->get_error_message(); // @phpstan-ignore-line
}
} else {
$text = __('Invalid license key length or missing characters. Please make sure to copy the 36 character license key here.', 'ajax-search-pro');
}
return array(
'success' => $success,
'text' => $text,
);
}
public function deactivate(): void {
$this->data['active'] = false;
update_option('_asp_media_service_data', $this->data);
}
public function delete(): void {
delete_option('_asp_media_service_data');
}
public function get(): string {
return !empty($this->data['license']) ? $this->data['license'] : '';
}
/**
* @return LicenseDataArr
*/
public function getData(): array {
return $this->data;
}
/**
* @param string $license
* @param bool $active
* @param LicenseStatsArr $stats
* @return void
*/
public function set( string $license, bool $active, array $stats ) {
$this->data = array(
'license' => $license,
'active' => $active,
'last_check' => time(),
'stats' => $stats,
);
update_option('_asp_media_service_data', $this->data);
}
/**
* @param LicenseStatsArr|false|array{} $stats
* @return void
*/
public function setStats( $stats = false ) {
if ( $stats !== false && count($stats) > 0 && $this->data['license'] !== false ) {
$this->data['stats'] = $stats;
update_option(
'_asp_media_service_data',
array(
'license' => $this->data['license'],
'active' => $this->data['active'],
'last_check' => time(),
'stats' => $stats,
)
);
}
}
}

View File

@@ -0,0 +1,126 @@
<?php
namespace WPDRMS\ASP\Media\RemoteService;
use WP_Error;
defined('ABSPATH') or die("You can't access this file directly.");
class Parser {
private $server = "https://mediaservices.ajaxsearchpro.com/";
private $filepath, $license, $file_url, $send_file, $stats = false, $max_filesize = 0;
function __construct( $filepath, $file_url, $license, $send_file = false, $max_filesize = 0 ) {
$this->filepath = $filepath;
$this->file_url = $file_url;
$this->license = $license;
$this->send_file = $send_file;
$this->max_filesize = $max_filesize;
}
function request() {
$url = $this->getServer();
if ( $this->getServer() !== false ) {
$response = $this->post( $url );
if ( is_wp_error($response) ) {
return $response;
}
$data = json_decode($response['body'], true);
$this->stats = $data['stats'];
if ( $data['success'] == 0 ) {
return new WP_Error($data['code'], $data['text']);
} else {
return $data['text'];
}
}
}
function getStats() {
return $this->stats;
}
private function post( $url ) {
if ( $this->send_file ) {
$file = fopen( $this->filepath, 'r' );
if ( false === $file ) {
$response = new WP_Error( 'fopen', 'Could not open the file for reading.' );
} else {
$file_size = filesize( $this->filepath );
if ( $file_size === false ) {
$response = new WP_Error( 'fsize_error', 'Local server could not determine the file size.' );
} else if ( $this->max_filesize > 0 && ($file_size / 1048576 ) > $this->max_filesize ) {
$response = new WP_Error( 'fsize_too_big', 'Local file size check: File too big ('.number_format($file_size / 1048576, 2) . ' MB'.')' );
} else {
$file_data = fread( $file, $file_size );
$response = wp_safe_remote_post(
$url,
array_merge(
$this->multipartPostData($file_data, $this->filepath, array(
'license' => $this->license
)), array(
'timeout' => 60
)
)
);
}
}
} else {
$response = wp_safe_remote_post(
$url,
array(
'body' => array(
'license' => $this->license,
'url' => $this->file_url
)
)
);
}
return $response;
}
private function getServer() {
$address = get_transient('_asp_media_server_address');
if ( $address === false ) {
$server = wp_safe_remote_get($this->server);
if ( !is_wp_error($server) ) {
$address = trim($server['body']);
set_transient('_asp_media_server_address', $address);
} else {
$address = false;
}
}
return $address;
}
private function multipartPostData($file_contents, $file_name, $post_fields = array()) {
$boundary = wp_generate_password( 24 );
$headers = array(
'content-type' => 'multipart/form-data; boundary=' . $boundary,
);
$payload = '';
foreach ( $post_fields as $name => $value ) {
$payload .= '--' . $boundary;
$payload .= "\r\n";
$payload .= 'Content-Disposition: form-data; name="' . $name .
'"' . "\r\n\r\n";
$payload .= $value;
$payload .= "\r\n";
}
if ( strlen($file_contents) > 0 ) {
$payload .= '--' . $boundary;
$payload .= "\r\n";
$payload .= 'Content-Disposition: form-data; name="' . 'file' .
'"; filename="' . basename( $file_name ) . '"' . "\r\n";
$payload .= "\r\n";
$payload .= $file_contents;
$payload .= "\r\n";
}
$payload .= '--' . $boundary . '--';
return array(
'headers' => $headers,
'body' => $payload,
);
}
}