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.
Related
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
I am using imap_mail_move() to move emails from one folder to another. This works pretty well, but not if it comes to special characters in the folder name. I am sure I need to encode the name, but all test where not succesful.
Anybody that has a nice idea? Thanks in advance.
class EmailReader {
[...]
function doMoveEmail($uid, $targetFolder) {
$targetFolder = imap_utf8_to_mutf7($targetFolder);
$return = imap_mail_move($this->conn, $uid, $targetFolder, CP_UID);
if (!$return) {
$this->printValue(imap_errors());
die("stop");
}
return $return;
}
[...]
}
Calling the function in the script
[...]
$uid = 1234;
$folderTarget1 = "INBOX.00_Korrespondenz";
$this->doMoveEmail($uid, $folderTarget1);
$folderTarget2 = "INBOX.01_Anmeldevorgang.011_Bestätigungslink";
$this->doMoveEmail($uid, $folderTarget2);
[...]
The execution of the first call (folderTarget1) is working pretty well.
The execution of the secound call (folderTarget2) is creating an error:
[TRYCREATE] Mailbox doesn't exist: INBOX.01_Anmeldevorgang.011_Bestätigungslink (0.001 + 0.000 secs).
Remark 1:
if I call imap_list(), the name of the folder is shown as
"INBOX.01_Anmeldevorgang.011_Besta&Awg-tigungslink" (=$val)
using:
$new = mb_convert_encoding($val,'UTF-8','UTF7-IMAP')
echo $new; // gives --> "INBOX.01_Anmeldevorgang.011_Bestätigungslink"
but:
$new2 = mb_convert_encoding($new,'UTF7-IMAP', 'UTF-8')
echo $new2; // gives --> "INBOX.01_Anmeldevorgang.011_Best&AOQ-tigungslink"
Remark 2
I checked each possible encoding, with the following script, but none of them matchs the value that is returned by imap_list().
// looking for "INBOX.01_Anmeldevorgang.011_Besta&Awg-tigungslink" given by imap_list().
$targetFolder = "INBOX.01_Anmeldevorgang.011_Bestätigungslink";
foreach(mb_list_encodings() as $chr){
echo mb_convert_encoding($targetFolder, $chr, 'UTF-8')." : ".$chr."<br>";
}
Your folder name, as on the server, Besta&Awg-tigungslink is not canonically encoded:
&Awg- decodes as the combining diaereses character. Using some convenient python to look it up:
import base64
import unicode data
x = base64.b64decode('Awg=').decode('utf-16be'); # equals added to satisfy base64 padding requirements
unicodedata.name(x)
# Returns 'COMBINING DIAERESIS'
This combines with the a in front of it to show ä.
Your encoder is returning the more common precomposed form:
x = base64.b64decode('AOQ=').decode('utf-16be')
unicodedata.name(x)
# Returns: 'LATIN SMALL LETTER A WITH DIAERESIS'
This is a representation of ä directly.
Normally, when you work with IMAP folders, you pass around the raw name, and only convert the folder name for display. As you can see, there is not necessarily a one-way mapping from glyphs to encodings in unicode.
It does surprise me that PHP does seem to be doing a canonicalization step when encoding; I would expect round tripping the same data to return the same thing.
I created a workaround, which helps me to work with UTF8-values and to translate it to the original (raw) IMAP folder name.
function getFolderList() {
$folders = imap_list($this->conn, "{".$this->server."}", "*");
if (is_array($folders)) {
// Remove Server details of each element of array
$folders = array_map(function($val) { return str_replace("{".$this->server."}","",$val); }, $folders);
// Sort array
asort($folders);
// Renumber the list
$folders = array_values($folders);
// add UTF-8 encoded value to array
// this is needed as the original value is so wiered, that it is not possible to encode it
// with a function on the fly. This additional utf-8 value is needed to map the utf-8 value
// to the original value. The original value is still needed to do some operations like e.g.:
// - imap_mail_move()
// - imap_reopen()
// ==> the trick is to use normalizer_normalize()
$return = array();
foreach ($folders as $key => $folder) {
$return[$key]['original'] = $folder;
$return[$key]['utf8'] = normalizer_normalize(mb_convert_encoding($folder,'UTF-8','UTF7-IMAP'));
}
return $return;
} else {
die("IMAP_Folder-List failed: " . imap_last_error() . "\n");
}
}
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);
I get the following error:
Catchable fatal error: Argument 1 passed to CorenlpAdapter::getOutput() must be an instance of string, string given, called in /Library/WebServer/Documents/website/php-stanford-corenlp-adapter/index.php on line 22 and defined in /Library/WebServer/Documents/website/php-stanford-corenlp-adapter/src/CoreNLP/CorenlpAdapter.php on line 95
index.php 21 and 22 contain:
$text1 = 'I will meet Mary in New York at 10pm';
$coreNLP->getOutput($text1);
corenlpAdapter.php lines 95 and onwards contain:
public function getOutput(string $text){
if(ONLINE_API){
// run the text through the public API
$this->getServerOutputOnline($text);
} else{
// run the text through Java CoreNLP
$this->getServerOutput($text);
}
// cache result
$this->serverMemory[] = $this->serverOutput;
if(empty($this->serverOutput)){
echo '** ERROR: No output from the CoreNLP Server **<br />
- Check if the CoreNLP server is running. Start the CoreNLP server if necessary<br />
- Check if the port you are using (probably port 9000) is not blocked by another program<br />';
die;
}
/**
* create trees
*/
$sentences = $this->serverOutput['sentences'];
foreach($this->serverOutput['sentences'] as $sentence){
$tree = $this->getTreeWithTokens($sentence); // gets one tree
$this->trees[] = $tree; // collect all trees
}
/**
* add OpenIE data
*/
$this->addOpenIE();
// to get the trees just call $coreNLP->trees in the main program
return;
}
Why exactly am I getting this error when text1 is a string?
I am the original author of this class. As you can see, the function getOutput looks like this:
public function getOutput(string $text){
...
}
Change that to:
public function getOutput($text){
...
}
The function tries to enforce that the input is string. The original code should work. However, it seems that in your case, PHP thinks "string" is not actually a string. Maybe the coding environment (the IDE) you are using uses the wrong character set? Or maybe you copy-pasted the code from HTML into the IDE or something like that. Thus whilst it says "string" on the screen, it's not actually a string to PHP.
If you are sure the input is a string, you can safely change the code like above. The class should then work normally.
public function getOutput($text){
.
.
.
}
I built a PHP file with the sole purpose of hiding the API keys for Google Search, but part of the file_get_contents() always echo angular.callbacks._0_({ instead of angular.callbacks._0({
This small change makes the rest of the response worthless as Angular throws Uncaught TypeError: angular.callbacks._0_ is not a function. Although the workaround does works flawlessly, I would like to know if someone found the root of this issue or a better solution that is strictly PHP (no curl or any other package.)
search.php
<?php // Created by Deovandski on 2/14/2016
header('Content-type: application/json');
# Setup Base URL and array for Parameters
$host = 'https://www.googleapis.com/customsearch/v1?';
$queries = array();
$queries['cx'] = "XXX";// CSE KEY
$queries['key'] = "XXX"; // API KEY
# Setup possible incoming params
if (isset($_GET['search_term'])) $queries['q'] = $_GET['search_term'];
if (isset($_GET['result_count'])) $queries['result_count'] = $_GET['result_count'];
if (isset($_GET['callback'])) $queries['callback'] = $_GET['callback'];
# Build query and Final URL
$queriesURL = http_build_query($queries) . "\n";
$finalURL = $host.$queriesURL;
echo $finalURL;
/* echo $finalURL output (I only edited the keys out):
https://www.googleapis.com/customsearch/v1?cx=XXX&key=XXX&q=Hatsune+Miku&result_count=10&callback=angular.callbacks._0
*/
// Setup Response
$response = file_get_contents($finalURL);
// workaround
$fixedResponse = str_replace("angular.callbacks._0_", "angular.callbacks._0", $response);
echo $fixedResponse;
?>
This is part of a correct Google API response:
// API callback
angular.callbacks._0({
"kind": "customsearch#search",
"url": {
"type": "application/json",
"template": "https://www.googleapis.com/customsearch/v1?q={searchTerms}&num={count?}&start={startIndex?}&lr={language?}&safe={safe?}&cx={cx?}&cref={cref?}&sort={sort?}&filter={filter?}&gl={gl?}&cr={cr?}&googlehost={googleHost?}&c2coff={disableCnTwTranslation?}&hq={hq?}&hl={hl?}&siteSearch={siteSearch?}&siteSearchFilter={siteSearchFilter?}&exactTerms={exactTerms?}&excludeTerms={excludeTerms?}&linkSite={linkSite?}&orTerms={orTerms?}&relatedSite={relatedSite?}&dateRestrict={dateRestrict?}&lowRange={lowRange?}&highRange={highRange?}&searchType={searchType}&fileType={fileType?}&rights={rights?}&imgSize={imgSize?}&imgType={imgType?}&imgColorType={imgColorType?}&imgDominantColor={imgDominantColor?}&alt=json"
},
I put up a live version of this issue that can be seen on my FTP server. The PHP file can be viewed through this link (AngularJS parameters included on it).
The problem is the escape sequences \n. Which is passed as part of the request. And which is interpreted as space and as part of the callback function name and replaced by the side of the API to underline.
To understand just try this option and look at the result:
$queriesURL = http_build_query($queries) . "\n" . "after";
So just take away a newline.