PHP COM('word.application') functions and methods parameters issue - php

I'm using php and new COM('word.application') to manipulate a .doc file (add and edit fields), I'm on a Windows Server 2012 R2 with apache 2.4.25 x64 and php 7.0.17 x64.
So far I made a simple code which work great, but I have an issue when using functions with multiple parameters like this one
$word->Documents[1]->Protect(3, false, 'mypassword', false, false);
I get the following error :
[Erreur] Impossible de lancer le connecteur Microsoft Office Word. : com_exception: Parameter 4: Le type ne correspond pas
Approx translation in english :
[Error] Impossible to laungh the Microsoft Office Word connector : com_exception: Parameter 4: type does not correspond
In VBA code like this it's working
ActiveDocument.Protect Type:=wdAllowOnlyFormFields, NoReset:=True
According to the doc found in VBA
t.com/en-us/library/office/aa220366(v=office.11).aspx
Parameters 2, 4 and 5 should be booleans, so I don't know why I'm getting this error. If i remove parameters 4 and 5, I get the same error but on parameter 2.
With some searches i looked at the object new VARIANT() but it didn't work too.
I have also the problem with $this->Documents1->SaveAs()
$fileName = "D:\\test.doc";
$this->Documents[1]->SaveAs($fileName);
It works.
According to the doc (https://msdn.microsoft.com/en-us/library/microsoft.office.tools.word.document.saveas(v=vs.120).aspx) I should be able to use a second parameter like this :
$fileName = "D:\\test.doc";
$fileFormat = 0;
$this->Documents[1]->SaveAs($fileName, $fileFormat);
Like this I have the error "Parameter 0: Le type ne correspond pas" (Type does not correspond).
In VBA it's working differently, maybe I'm doing something wrong ...
Much thanks if you help me :)
edit : full example code
<?php
$word = new COM("word.application") or die("Cannot create Word object");
echo "com object created<br>";
$word->Visible = false;
$word->WindowState = 2;
$word->DisplayAlerts = false;
$word->Documents->Open("D:\\test.doc");
echo "document loaded<br>";
$import = $word->Documents[1]->VBProject->VBComponents->Import("D:\\fr.mac");
$import->name = "Macrofr";
$myModule = $word->Documents[1]->VBProject->VBComponents->Import("D:\\test1.mac");
$myModule->name = "MacroInit";
$myModule2 = $word->Documents[1]->VBProject->VBComponents->Import("D:\\test2.mac");
$myModule2->name = "MacroFonction";
$myModule3 = $word->Documents[1]->VBProject->VBComponents->Import("D:\\test3.frm");
$myModule3->name = "MacroProgression";
$word->Documents[1]->Protect(3,false,'motdepasse',false,false); // (type de protection,noReset,mdp)
$word->Documents[1]->SaveAs("D:\\doc_with_macro.doc", 0);
$word->Documents[1]->Close();
echo "closing doc<br>";
$word->Quit();
$word = null;
echo "end<br>";

The only problem that I see in this code is related to protect function. First parameter must be VARIANT like this :
$word->Documents[1]->Protect(
new VARIANT(3, VT_I4),
false,
'Your password',
false,
true
);

Related

Read Windows Installer (MSI file) attributes from PHP

I have a Windows MSI file, that I need to programmatically read the version number from. The only place I can see this version is within the Subject of the file details:
If I somehow can read the entire content of Subject this would be fine but is there any way to get this from PHP? The PHP is running in an IIS web server, if this helps ;-)
stat is of no help for this.
I considered doing a checksum of the file, and can do that, but I really need the real version.
For now I am unable to find a native PHP solution for this, so I have temporarily solved this by calling a Powershell script as it seems easier to do in there.
I am now having this PHP code:
$version = exec("powershell.exe -file GetMsiVersion.ps1 MyFile.msi);
With my above picture then $version will contain 1.5.9, so I will not even require to interpret the data from Subject.
The GetMsiVersion.ps1 Powershell script has this code:
function Get-Property ($Object, $PropertyName, [object[]]$ArgumentList) {
return $Object.GetType().InvokeMember($PropertyName, 'Public, Instance, GetProperty', $null, $Object, $ArgumentList)
}
function Invoke-Method ($Object, $MethodName, $ArgumentList) {
return $Object.GetType().InvokeMember($MethodName, 'Public, Instance, InvokeMethod', $null, $Object, $ArgumentList)
}
$Path = $args[0]
$msiOpenDatabaseModeReadOnly = 0
$Installer = New-Object -ComObject WindowsInstaller.Installer
$Database = Invoke-Method $Installer OpenDatabase #($Path, $msiOpenDatabaseModeReadOnly)
$View = Invoke-Method $Database OpenView #("SELECT Value FROM Property WHERE Property='ProductVersion'")
Invoke-Method $View Execute
$Record = Invoke-Method $View Fetch
if ($Record) {
Write-Output (Get-Property $Record StringData 1)
}
Invoke-Method $View Close #()
I will accept this as the best solution here-and-now but, I hope this can be archieved natively from PHP as I see this as a better and more clean solution - and by then I will accept that as the best answer (or at least unaccept my own temporary solution).
Doing an exec is a little evil ;-)
Two things first:
I have never accessed COM from PHP, but below are some VBScript samples of getting information from MSI files by using MSI API - COM automation. There are also Win32 functions.
That field you refer to is not a version field, but a text field from the MSI's "Summary Stream" - a special part of the MSI file with "meta information" of various kinds. Summary Information Stream (the full name).
Here is how you can get the REAL version of an MSI file. This is stored in the property "ProductVersion" in the MSI file. There are at least two different ways to retrieve it - by opening the MSI file as a session or just access the property table via SQL query:
Access version via Session object:
Const msiUILevelNone = 2
Dim installer : Set installer = CreateObject("WindowsInstaller.Installer")
installer.UILevel = msiUILevelNone
Set s = installer.OpenPackage("C:\MySetup.msi",1)
MsgBox CStr(s.ProductProperty("ProductVersion"))
Accessing version via SQL agains MSI database (property table):
Dim installer : Set installer = CreateObject("WindowsInstaller.Installer")
' Open MSI database in read-only mode (0)
Set db = installer.OpenDatabase("C:\MySetup.msi", 0)
Set view = db.OpenView("SELECT `Value` FROM `Property` WHERE `Property`='ProductVersion'")
view.Execute
Set record = view.Fetch
MsgBox CStr(record.StringData(1))
Then there is the issue of accessing the SummaryStream - which is what you are really asking by the looks of it - here is a simple smoke test with some hints as to what properties you can retrieve - be careful with the summary stream - corruption is possible in various ways (I don't recall the details, but it should be safe to access read-only):
Dim installer : Set installer = CreateObject("WindowsInstaller.Installer")
' Open MSI database in read-only mode (0)
Set db = installer.OpenDatabase("C:\MySetup.msi", 0)
MsgBox CStr(db.SummaryInformation.Property(3))
' 1 = "Codepage"
' 2 = "Title"
' 3 = "Subject"
' 4 = "Author"
' 5 = "Keywords"
' 6 = "Comments"
' 7 = "Template"
' 8 = "LastAuthor"
' 9 = "Revision"
' 11 = "Printed"
' 12 = "Created"
' 13 = "Saved"
' 14 = "Pages"
' 15 = "Words"
' 16 = "Characters"
' 18 = "Application"
' 19 = "Security"
Links:
Modify an MSI using MSI SQL VBScript WiRunSQL.vbs
List tables in MSI file using VBScript

Onlinecity SMPP - add new tlv parameters

I am using the OnlineCity SMPP client lib for sending SMS. It was working fine. But as per the new guideline of TRAI, we need to add the following new TLV parameters while sending SMS
group = smpp-tlv
name = EntityID
tag = 0x1400
type = octetstring
length = 30
smsc-id = ***
I tried this
// Prepare message
$ENTITY_ID = new SmppTag(0x1400, '****************');
$tags = array($ENTITY_ID);
$from = new SmppAddress($SMS_Params['senderid'],SMPP::TON_ALPHANUMERIC);
$to = new SmppAddress($SMS_Params['phone'],SMPP::TON_INTERNATIONAL,SMPP::NPI_E164);
$encodedMessage = utf8_encode($SMS_Params['message']);
// Send
$return_data = $smpp->sendSMS($from,$to,$encodedMessage,$tags);
I got the success response but didn't get any SMS. I checked with my smpp provider. They said that the additional TLV parameter is not there and that's why the SMS is not sent.
Do you guys have any idea, can we do it in my current code based on onlinecity library or should I do something else?.
You need to check if your octect strings are null terminated or not, by default the library is assuming it will be. So there is a variable $sms_null_terminate_octetstrings which needs to be reset if your provider does not end with null.
The above code change that Asterisk integrator has recommended says the same thing.
Rather than changing the code, if you can reset the flag based on your need, that should solve the problem.
For others who wanted to add new mandatory parameters should add like this using smpp-php library.
$tags = array(
new SmppTag(0x1400, your_pe_id),
new SmppTag(0x1401, your_template_id)
);
$message_id = $smpp->sendSMS($from, $to, $encodedMessage, $tags);
Remove "+(self::$sms_null_terminate_octetstrings ? 1 : 0)" from smppclient.class.php file
Actual Code :
$pdu = pack('a1cca'.(strlen($source->value)+1).'cca'.(strlen($destination->value)+1).'ccc'.($scheduleDeliveryTime ? 'a16x' : 'a1').($validityPeriod ? 'a16x' : 'a1').'ccccca'.(strlen($short_message)+(self::$sms_null_terminate_octetstrings ? 1 : 0))
Updated Code :
$pdu = pack('a1cca'.(strlen($source->value)+1).'cca'.(strlen($destination->value)+1).'ccc'.($scheduleDeliveryTime ? 'a16x' : 'a1').($validityPeriod ? 'a16x' : 'a1').'ccccca'.(strlen($short_message))

is there an API for PHP_CodeSniffer?

Does PHP_CodeSniffer have an API I can use, rather than run the command line?
So given a string of PHP code, $code, and Composer ensuring loading of the PHP_CodeSniffer code, is there something I can do such as:
// Pseudocode!
$code_sniffer = new PHPCodeSniffer;
$result = $code_sniffer->sniff($code);
rather than go via the command line with something like:
$result = exec(sprintf('echo %s | vendor/bin/phpcs', escapeshellarg($code)));
There is, but you need to write more code than that.
Take a look at the example here: https://gist.github.com/gsherwood/aafd2c16631a8a872f0c4a23916962ac
That example allows you to sniff a piece of code that isn't written to a file yet, and set up things like the standard to use (could also be a ruleset.xml file).
Another example, that uses the JS tokenizer instead of PHP + pulls content from an existing file, is available here: https://gist.github.com/gsherwood/f17cfc90f14a9b29eeb6b2e99e6e7f66
It is shorter because it doesn't use as many options.
Here's what I got working for PHPCS 2:
PHP_CodeSniffer::setConfigData(
'installed_paths',
// The path to the standard I am using.
__DIR__ . '/../../vendor/drupal/coder/coder_sniffer',
TRUE
);
$phpcs = new PHP_CodeSniffer(
// Verbosity.
0,
// Tab width
0,
// Encoding.
'iso-8859-1',
// Interactive.
FALSE
);
$phpcs->initStandard('Drupal');
// Mock a PHP_CodeSniffer_CLI object, as the PHP_CodeSniffer object expects
// to have this and be able to retrieve settings from it.
// (I am using PHPCS within tests, so I have Prophecy available to do this. In another context, just creating a subclass of PHP_CodeSniffer_CLI set to return the right things would work too.)
$prophet = new \Prophecy\Prophet;
$prophecy = $prophet->prophesize();
$prophecy->willExtend(\PHP_CodeSniffer_CLI::class);
// No way to set these on the phpcs object.
$prophecy->getCommandLineValues()->willReturn([
'reports' => [
"full" => NULL,
],
"showSources" => false,
"reportWidth" => null,
"reportFile" => null
]);
$phpcs_cli = $prophecy->reveal();
// Have to set these properties, as they are read directly, e.g. by
// PHP_CodeSniffer_File::_addError()
$phpcs_cli->errorSeverity = 5;
$phpcs_cli->warningSeverity = 5;
// Set the CLI object on the PHP_CodeSniffer object.
$phpcs->setCli($phpcs_cli);
// Process the file with PHPCS.
$phpcsFile = $phpcs->processFile(NULL, $code);
$errors = $phpcsFile->getErrorCount();
$warnings = $phpcsFile->getWarningCount();
$total_error_count = ($phpcsFile->getErrorCount() + $phpcsFile->getWarningCount());
// Get the reporting to process the errors.
$this->reporting = new \PHP_CodeSniffer_Reporting();
$reportClass = $this->reporting->factory('full');
// This gets you an array of all the messages which you can easily work over.
$reportData = $this->reporting->prepareFileReport($phpcsFile);
// This formats the report, but prints it directly.
$reportClass->generateFileReport($reportData, $phpcsFile);

How do I use ResourceBundle::getLocales()?

Given I have a directory /locales with the files root.res and es.res, which have been generated (using genrb) from the text files as follows:
File: root.txt
root:table {
test:string { "My test text" }
}
File: es.txt
es:table {
test:string { "Mi texto de prueba" }
}
When I run the following code:
<?php
$bundleName = 'locale';
$resourceBundle = new ResourceBundle('es', $bundleName);
echo join("\n", $resourceBundle->getLocales($bundleName));
I should see:
es
root
However, the code produces:
Warning: join(): Invalid arguments passed...
This happens because $resourceBundle ->getLocales($bundleName) returns bool(false), but running the following code for locale es:
<?php
$bundleName = 'locale';
$resourceBundle = new ResourceBundle('es', $bundleName);
echo ($resourceBundle->get('test') . "\n");
correctly outputs:
Mi texto de prueba
And running for locale root:
<?php
$bundleName = 'locale';
$resourceBundle = new ResourceBundle('root', $bundleName);
echo ($resourceBundle->get('test') . "\n");
correctly outputs:
My test text
What do I need to do to make getLocales() work?
So, the PHP Manual, as usual, is not clear about this. However, the ICU Web site is:
A resource bundle is a set of pairs that provide a mapping from key to value. A given program can have different sets of resource bundles; one set for error messages, one for menus, and so on. However, the program may be organized to combine all of its resource bundles into a single related set.
The set is organized into a tree with "root" at the top, the language at the first level, the country at the second level, and additional variants below these levels. The set must contain a root that has all keys that can be used by the program accessing the resource bundles.
I will return with an update when tested.

net.sf.jasperreports.engine.design.JRValidationException - Query parameter not found

I use JasperServer and PHP JavaBridge to generate PDF reports via JasperServer inside PHP. I get compile error because of missing (unassigned) parameter passed to JRXML compiler
Fatal error: Uncaught [[o:Exception]:
"java.lang.Exception: Invoke failed:
[[c:JasperCompileManager]]->compileReport((o:String)[o:String]).
Cause: net.sf.jasperreports.engine.design.JRValidationException:
**Report design not valid** : 1. **Query parameter not found** : db_field_id VM:
1.6.0_18#http://java.sun.com/" at: #-12
net.sf.jasperreports.engine.design.JRAbstractCompiler.verifyDesign(JRAbstractCompiler.java:258)
I cant find a way to pass my
$params = new Java("java.util.HashMap");
foreach ($jrxml_params as $key => $jr_param) $params->put($key, $jr_param);
list of params to the compile method nor I can disable this verification by
$japser_props = new JavaClass("net.sf.jasperreports.engine.util.JRProperties");
$japser_props->COMPILER_XML_VALIDATION = false;
Here is what I use to generate PDF (works fine if JRXML file doesn't contain $P{} pamareters and halts otherwise)
$class = new JavaClass("java.lang.Class");
$class->forName("com.mysql.jdbc.Driver");
$driverManager = new JavaClass("java.sql.DriverManager");
$conn = $driverManager->getConnection("jdbc:mysql://localhost:3306/XXX?user=XXX&password=1234");
$compileManager = new JavaClass("net.sf.jasperreports.engine.JasperCompileManager");
$report = $compileManager->compileReport(realpath("/www/some.jrxml"));
$params = new Java("java.util.HashMap");
foreach ($jrxml_params as $key => $jr_param) $params->put($key, $jr_param);
$jasperPrint = $fillManager->fillReport($report, $params, $conn);
$exportManager = new JavaClass("net.sf.jasperreports.engine.JasperExportManager");
$outputPath = realpath(".")."/"."output.pdf";
$exportManager->exportReportToPdfFile($jasperPrint, $outputPath);
How do I avoid this error, I know what I need to pass and I don't know a way to do it, can't I just pass params to fillManager?
$japser_props = new JavaClass("net.sf.jasperreports.engine.util.JRProperties");
$japser_props->setProperty('net.sf.jasperreports.compiler.xml.validation',true);
this is the way to set property from PHP but that's not the problem. It turns out everything was fine, I've missed parameter declaration before my MySQL query... Put
<parameter name="db_field_id" class="java.lang.Integer">
in your JRXML before you use it as $P{db_field_id} now it compliles fine and later
$jasperPrint = $fillManager->fillReport($report, $params, $conn);
parameters are assigned at fill time

Categories