protected function _decryptAndVerify($data, $isFile, $outputFile, $ignoreVerifyErrors = false)
    {
        $input  = $this->_prepareInput($data, $isFile, false);
        $output = $this->_prepareOutput($outputFile, $input);

        $this->engine->reset();
        $this->engine->setPins($this->decryptKeys);
        $this->engine->setInput($input);
        $this->engine->setOutput($output);
        $this->engine->setOperation('--decrypt');
        $this->engine->setProcessData('IgnoreVerifyErrors', $ignoreVerifyErrors);
        $this->engine->run();

        $return = array(
            'data'       => null,
            'signatures' => $this->engine->getProcessData('Signatures')
        );

        if ($outputFile === null) {
            $return['data'] = $output;
        }

        return $return;
    }

protected function _decryptAndVerify ( $ data , $ isFile , $ outputFile , $ ignoreVerifyErrors = false ) { $ input = $ this -> _prepareInput ( $ data , $ isFile , false ) ; $ output = $ this -> _prepareOutput ( $ outputFile , $ input ) ; $ this -> engine -> reset ( ) ; $ this -> engine -> setPins ( $ this -> decryptKeys ) ; $ this -> engine -> setInput ( $ input ) ; $ this -> engine -> setOutput ( $ output ) ; $ this -> engine -> setOperation ( '--decrypt' ) ; $ this -> engine -> setProcessData ( 'IgnoreVerifyErrors' , $ ignoreVerifyErrors ) ; $ this -> engine -> run ( ) ; $ return = array ( 'data' => null , 'signatures' => $ this -> engine -> getProcessData ( 'Signatures' ) ) ; if ( $ outputFile === null ) { $ return [ 'data' ] = $ output ; } return $ return ; }

Decrypts and verifies encrypted, signed data

@param string  $data               the encrypted signed data to be decrypted and
verified.
@param boolean $isFile             whether or not the data is a filename.
@param string  $outputFile         the name of the file to which the decrypted
data should be written. If null, the decrypted
data is returned in the results array.
@param boolean $ignoreVerifyErrors enables ignoring of signature verification
errors caused by missing public key.
When enabled Crypt_GPG_KeyNotFoundException
will not be thrown.

@return array two element array. The array has an element 'data'
containing the decrypted data and an element
'signatures' containing an array of
{@link Crypt_GPG_Signature} objects for the signed data.
If the decrypted data is written to a file, the 'data'
element is null.

@throws Crypt_GPG_KeyNotFoundException if the private key needed to
decrypt the data is not in the user's keyring or if the public
key needed for verification is not in the user's keyring.

@throws Crypt_GPG_NoDataException if specified data does not contain
GPG signed, encrypted data.

@throws Crypt_GPG_BadPassphraseException if a required passphrase is
incorrect or if a required passphrase is not specified. See
{@link Crypt_GPG::addDecryptKey()}.

@throws Crypt_GPG_FileException if the output file is not writeable or
if the input file is not readable.

@throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
Use the <kbd>debug</kbd> option and file a bug report if these
exceptions occur.

@see Crypt_GPG_Signature

Decrypts and verifies encrypted signed data


protected function _prepareInput($data, $isFile = false, $allowEmpty = true)
    {
        if ($isFile) {
            $input = @fopen($data, 'rb');
            if ($input === false) {
                throw new Crypt_GPG_FileException(
                    'Could not open input file "' . $data . '"',
                    0,
                    $data
                );
            }
        } else {
            $input = strval($data);
            if (!$allowEmpty && $input === '') {
                throw new Crypt_GPG_NoDataException(
                    'No valid input data found.',
                    self::ERROR_NO_DATA
                );
            }
        }

        return $input;
    }

protected
function
_prepareInput
(
$
data
,
$
isFile
=
false
,
$
allowEmpty
=
true
)
{
if
(
$
isFile
)
{
$
input
=
@
fopen
(
$
data
,
'rb'
)
;
if
(
$
input
===
false
)
{
throw
new
Crypt_GPG_FileException
(
'Could not open input file "'
.
$
data
.
'"'
,
0
,
$
data
)
;
}
}
else
{
$
input
=
strval
(
$
data
)
;
if
(
!
$
allowEmpty
&&
$
input
===
''
)
{
throw
new
Crypt_GPG_NoDataException
(
'No valid input data found.'
,
self
::
ERROR_NO_DATA
)
;
}
}
return
$
input
;
}

Prepares command input

@param string  $data       the input data.
@param boolean $isFile     whether or not the input is a filename.
@param boolean $allowEmpty whether to check if the input is not empty.

@throws Crypt_GPG_NoDataException if the key data is missing.
@throws Crypt_GPG_FileException if the file is not readable.


protected function _prepareOutput($outputFile, $input = null)
    {
        if ($outputFile === null) {
            $output = '';
        } else {
            $output = @fopen($outputFile, 'wb');
            if ($output === false) {
                if (is_resource($input)) {
                    fclose($input);
                }
                throw new Crypt_GPG_FileException(
                    'Could not open output file "' . $outputFile . '"',
                    0,
                    $outputFile
                );
            }
        }

        return $output;
    }

protected
function
_prepareOutput
(
$
outputFile
,
$
input
=
)
{
if
(
$
outputFile
===
)
{
$
output
=
''
;
}
else
{
$
output
=
@
fopen
(
$
outputFile
,
'wb'
)
;
if
(
$
output
===
false
)
{
if
(
is_resource
(
$
input
)
)
{
fclose
(
$
input
)
;
}
throw
new
Crypt_GPG_FileException
(
'Could not open output file "'
.
$
outputFile
.
'"'
,
0
,
$
outputFile
)
;
}
}
return
$
output
;
}

Prepares command output

@param string  $outputFile the name of the file in which the output
data should be stored. If null, the output
data is returned as a string.
@param boolean $input      the input resource, in case it would need
to be released (closed) on exception.

@throws Crypt_GPG_FileException if the file is not writeable.


protected function _getKeys($keyId = '')
    {
        // get private key fingerprints
        if ($keyId == '') {
            $operation = '--list-secret-keys';
        } else {
            $operation = '--utf8-strings --list-secret-keys ' . escapeshellarg($keyId);
        }

        // According to The file 'doc/DETAILS' in the GnuPG distribution, using
        // double '--with-fingerprint' also prints the fingerprint for subkeys.
        $arguments = array(
            '--with-colons',
            '--with-fingerprint',
            '--with-fingerprint',
            '--fixed-list-mode'
        );

        $output = '';

        $this->engine->reset();
        $this->engine->setOutput($output);
        $this->engine->setOperation($operation, $arguments);
        $this->engine->run();

        $privateKeyFingerprints = array();

        foreach (explode(PHP_EOL, $output) as $line) {
            $lineExp = explode(':', $line);
            if ($lineExp[0] == 'fpr') {
                $privateKeyFingerprints[] = $lineExp[9];
            }
        }

        // get public keys
        if ($keyId == '') {
            $operation = '--list-public-keys';
        } else {
            $operation = '--utf8-strings --list-public-keys ' . escapeshellarg($keyId);
        }

        $output = '';

        $this->engine->reset();
        $this->engine->setOutput($output);
        $this->engine->setOperation($operation, $arguments);
        $this->engine->run();

        $keys   = array();
        $key    = null; // current key
        $subKey = null; // current sub-key

        foreach (explode(PHP_EOL, $output) as $line) {
            $lineExp = explode(':', $line);

            if ($lineExp[0] == 'pub') {

                // new primary key means last key should be added to the array
                if ($key !== null) {
                    $keys[] = $key;
                }

                $key = new Crypt_GPG_Key();

                $subKey = Crypt_GPG_SubKey::parse($line);
                $key->addSubKey($subKey);

            } elseif ($lineExp[0] == 'sub') {

                $subKey = Crypt_GPG_SubKey::parse($line);
                $key->addSubKey($subKey);

            } elseif ($lineExp[0] == 'fpr') {

                $fingerprint = $lineExp[9];

                // set current sub-key fingerprint
                $subKey->setFingerprint($fingerprint);

                // if private key exists, set has private to true
                if (in_array($fingerprint, $privateKeyFingerprints)) {
                    $subKey->setHasPrivate(true);
                }

            } elseif ($lineExp[0] == 'uid') {

                $string = stripcslashes($lineExp[9]); // as per documentation
                $userId = new Crypt_GPG_UserId($string);

                if ($lineExp[1] == 'r') {
                    $userId->setRevoked(true);
                }

                $key->addUserId($userId);

            }
        }

        // add last key
        if ($key !== null) {
            $keys[] = $key;
        }

        return $keys;
    }

Gets the available keys in the keyring

Calls GPG with the --list-keys command and grabs keys. See
the first section of doc/DETAILS in the
{@link http://www.gnupg.org/download/ GPG package} for a detailed
description of how the GPG command output is parsed.

@param string $keyId optional. Only keys with that match the specified
pattern are returned. The pattern may be part of
a user id, a key id or a key fingerprint. If not
specified, all keys are returned.

@return array an array of {@link Crypt_GPG_Key} objects. If no keys
match the specified $keyId an empty array is
returned.

@throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
Use the debug option and file a bug report if these
exceptions occur.

@see Crypt_GPG_Key


public function sendCommand($command)
    {
        if (array_key_exists(self::FD_COMMAND, $this->_openPipes)) {
            $this->_commandBuffer .= $command . PHP_EOL;
        }
    }

public function sendCommand ( $ command ) { if ( array_key_exists ( self :: FD_COMMAND , $ this -> _openPipes ) ) { $ this -> _commandBuffer .= $ command . PHP_EOL ; } }

Sends a command to the GPG subprocess over the command file-descriptor
pipe

@param string $command the command to send.

@return void

@sensitive $command

Sends a command to the GPG subprocess over the command file - descriptor pipe


public function reset()
    {
        $this->_operation      = '';
        $this->_arguments      = array();
        $this->_input          = null;
        $this->_message        = null;
        $this->_output         = '';
        $this->_commandBuffer  = '';

        $this->_statusHandlers = array();
        $this->_errorHandlers  = array();

        if ($this->_debug) {
            $this->addStatusHandler(array($this, '_handleDebugStatus'));
            $this->addErrorHandler(array($this, '_handleDebugError'));
        }

        $this->_processHandler = new Crypt_GPG_ProcessHandler($this);

        $this->addStatusHandler(array($this->_processHandler, 'handleStatus'));
        $this->addErrorHandler(array($this->_processHandler, 'handleError'));
    }

public function reset ( ) { $ this -> _operation = '' ; $ this -> _arguments = array ( ) ; $ this -> _input = null ; $ this -> _message = null ; $ this -> _output = '' ; $ this -> _commandBuffer = '' ; $ this -> _statusHandlers = array ( ) ; $ this -> _errorHandlers = array ( ) ; if ( $ this -> _debug ) { $ this -> addStatusHandler ( array ( $ this , '_handleDebugStatus' ) ) ; $ this -> addErrorHandler ( array ( $ this , '_handleDebugError' ) ) ; } $ this -> _processHandler = new Crypt_GPG_ProcessHandler ( $ this ) ; $ this -> addStatusHandler ( array ( $ this -> _processHandler , 'handleStatus' ) ) ; $ this -> addErrorHandler ( array ( $ this -> _processHandler , 'handleError' ) ) ; }

Resets the GPG engine, preparing it for a new operation

@return void

@see Crypt_GPG_Engine::run()
@see Crypt_GPG_Engine::setOperation()

Resets the GPG engine preparing it for a new operation


public function run()
    {
        if ($this->_operation === '') {
            throw new Crypt_GPG_InvalidOperationException(
                'No GPG operation specified. Use Crypt_GPG_Engine::setOperation() ' .
                'before calling Crypt_GPG_Engine::run().'
            );
        }

        $this->_openSubprocess();
        $this->_process();
        $this->_closeSubprocess();
    }

Runs the current GPG operation.

This creates and manages the GPG subprocess.
This will close input/output file handles.

The operation must be set with {@link Crypt_GPG_Engine::setOperation()}
before this method is called.

@return void

@throws Crypt_GPG_InvalidOperationException if no operation is specified.
@throws Crypt_GPG_Exception if an unknown or unexpected error occurs.

@see Crypt_GPG_Engine::reset()
@see Crypt_GPG_Engine::setOperation()


public function setOperation($operation, array $arguments = array())
    {
        $this->_operation = $operation;
        $this->_arguments = $arguments;

        $this->_processHandler->setOperation($operation);
    }

public function setOperation ( $ operation , array $ arguments = array ( ) ) { $ this -> _operation = $ operation ; $ this -> _arguments = $ arguments ; $ this -> _processHandler -> setOperation ( $ operation ) ; }

Sets the operation to perform

@param string $operation the operation to perform. This should be one
of GPG's operations. For example,
--encrypt, --decrypt,
--sign, etc.
@param array  $arguments optional. Additional arguments for the GPG
subprocess. See the GPG manual for specific
values.

@return void

@see Crypt_GPG_Engine::reset()
@see Crypt_GPG_Engine::run()


public function setPins(array $keys)
    {
        $envKeys = array();

        foreach ($keys as $keyId => $key) {
            $envKeys[$keyId] = is_array($key) ? $key['passphrase'] : $key;
        }

        $_ENV['PINENTRY_USER_DATA'] = json_encode($envKeys);
    }

public function setPins ( array $ keys ) { $ envKeys = array ( ) ; foreach ( $ keys as $ keyId => $ key ) { $ envKeys [ $ keyId ] = is_array ( $ key ) ? $ key [ 'passphrase' ] : $ key ; } $ _ENV [ 'PINENTRY_USER_DATA' ] = json_encode ( $ envKeys ) ; }

Sets the PINENTRY_USER_DATA environment variable with the currently
added keys and passphrases

Keys and passphrases are stored as an indexed array of passphrases
in JSON encoded to a flat string.

For GnuPG 2.x this is how passphrases are passed. For GnuPG 1.x the
environment variable is set but not used.

@param array $keys the internal key array to use.

@return void


public function getVersion()
    {
        if ($this->_version == '') {
            $options = array(
                'homedir' => $this->_homedir,
                'binary'  => $this->_binary,
                'debug'   => $this->_debug,
                'agent'   => $this->_agent,
            );

            $engine = new self($options);
            $info   = '';

            // Set a garbage version so we do not end up looking up the version
            // recursively.
            $engine->_version = '1.0.0';

            $engine->reset();
            $engine->setOutput($info);
            $engine->setOperation('--version');
            $engine->run();

            $matches    = array();
            $expression = '#gpg \(GnuPG[A-Za-z0-9/]*?\) (\S+)#';

            if (preg_match($expression, $info, $matches) === 1) {
                $this->_version = $matches[1];
            } else {
                throw new Crypt_GPG_Exception(
                    'No GnuPG version information provided by the binary "' .
                    $this->_binary . '". Are you sure it is GnuPG?'
                );
            }

            if (version_compare($this->_version, self::MIN_VERSION, 'lt')) {
                throw new Crypt_GPG_Exception(
                    'The version of GnuPG being used (' . $this->_version .
                    ') is not supported by Crypt_GPG. The minimum version ' .
                    'required by Crypt_GPG is ' . self::MIN_VERSION
                );
            }
        }

        return $this->_version;
    }

Gets the version of the GnuPG binary

@return string a version number string containing the version of GnuPG
being used. This value is suitable to use with PHP's
version_compare() function.

@throws Crypt_GPG_Exception if an unknown or unexpected error occurs.
Use the <kbd>debug</kbd> option and file a bug report if these
exceptions occur.

@throws Crypt_GPG_UnsupportedException if the provided binary is not
GnuPG or if the GnuPG version is less than 1.0.2.


public function getProcessData($name)
    {
        if ($this->_processHandler) {
            switch ($name) {
            case 'SignatureInfo':
                if ($data = $this->_processHandler->getData('SigCreated')) {
                    return new Crypt_GPG_SignatureCreationInfo($data);
                }
                break;

            case 'Signatures':
            case 'Warnings':
                return (array) $this->_processHandler->getData($name);

            default:
                return $this->_processHandler->getData($name);
            }
        }
    }

public function getProcessData ( $ name ) { if ( $ this -> _processHandler ) { switch ( $ name ) { case 'SignatureInfo' : if ( $ data = $ this -> _processHandler -> getData ( 'SigCreated' ) ) { return new Crypt_GPG_SignatureCreationInfo ( $ data ) ; } break ; case 'Signatures' : case 'Warnings' : return ( array ) $ this -> _processHandler -> getData ( $ name ) ; default : return $ this -> _processHandler -> getData ( $ name ) ; } } }

Get data from the last process execution.

@param string $name Data element name (e.g. 'SignatureInfo')

@return mixed
@see    Crypt_GPG_ProcessHandler::getData()

Get data from the last process execution .


public function setProcessData($name, $value)
    {
        if ($this->_processHandler) {
            $this->_processHandler->setData($name, $value);
        }
    }

public function setProcessData ( $ name , $ value ) { if ( $ this -> _processHandler ) { $ this -> _processHandler -> setData ( $ name , $ value ) ; } }

Set some data for the process execution.

@param string $name  Data element name (e.g. 'Handle')
@param mixed  $value Data value

@return void

Set some data for the process execution .


private function _closeSubprocess()
    {
        // clear PINs from environment if they were set
        $_ENV['PINENTRY_USER_DATA'] = null;

        if (is_resource($this->_process)) {
            $this->_debug('CLOSING GPG SUBPROCESS');

            // close remaining open pipes
            foreach (array_keys($this->_openPipes) as $pipeNumber) {
                $this->_closePipe($pipeNumber);
            }

            $status   = proc_get_status($this->_process);
            $exitCode = proc_close($this->_process);

            // proc_close() can return -1 in some cases,
            // get the real exit code from the process status
            if ($exitCode < 0 && $status && !$status['running']) {
                $exitCode = $status['exitcode'];
            }

            if ($exitCode > 0) {
                $this->_debug(
                    '=> subprocess returned an unexpected exit code: ' .
                    $exitCode
                );
            }

            $this->_process = null;
            $this->_pipes   = array();

            // close file handles before throwing an exception
            if (is_resource($this->_input)) {
                fclose($this->_input);
            }

            if (is_resource($this->_output)) {
                fclose($this->_output);
            }

            $this->_processHandler->throwException($exitCode);
        }

        $this->_closeAgentLaunchProcess();

        if ($this->_agentInfo !== null) {
            $parts = explode(':', $this->_agentInfo, 3);

            if (!empty($parts[1])) {
                $this->_debug('STOPPING GPG-AGENT DAEMON');

                $process = new Crypt_GPG_ProcessControl($parts[1]);

                // terminate agent daemon
                $process->terminate();

                while ($process->isRunning()) {
                    usleep(10000); // 10 ms
                    $process->terminate();
                }

                $this->_debug('GPG-AGENT DAEMON STOPPED');
            }

            $this->_agentInfo = null;
        }
    }

Closes the internal GPG subprocess

Closes the internal GPG subprocess. Sets the private class property
{@link Crypt_GPG_Engine::$_process} to null.

@return void

@see Crypt_GPG_Engine::_openSubprocess()
@see Crypt_GPG_Engine::$_process


private function _closeAgentLaunchProcess()
    {
        if (is_resource($this->_agentProcess)) {
            $this->_debug('CLOSING GPG-AGENT LAUNCH PROCESS');

            // close agent pipes
            foreach ($this->_agentPipes as $pipe) {
                fflush($pipe);
                fclose($pipe);
            }

            // close agent launching process
            proc_close($this->_agentProcess);

            $this->_agentProcess = null;
            $this->_agentPipes   = array();

            $this->_debug('GPG-AGENT LAUNCH PROCESS CLOSED');
        }
    }

private
function
_closeAgentLaunchProcess
(
)
{
if
(
is_resource
(
$
this
->
_agentProcess
)
)
{
$
this
->
_debug
(
'CLOSING GPG-AGENT LAUNCH PROCESS'
)
;
// close agent pipes
foreach
(
$
this
->
_agentPipes
as
$
pipe
)
{
fflush
(
$
pipe
)
;
fclose
(
$
pipe
)
;
}
// close agent launching process
proc_close
(
$
this
->
_agentProcess
)
;
$
this
->
_agentProcess
=
;
$
this
->
_agentPipes
=
array
(
)
;
$
this
->
_debug
(
'GPG-AGENT LAUNCH PROCESS CLOSED'
)
;
}
}

Closes a the internal GPG-AGENT subprocess

Closes the internal GPG-AGENT subprocess. Sets the private class property
{@link Crypt_GPG_Engine::$_agentProcess} to null.

@return void

@see Crypt_GPG_Engine::_openSubprocess()
@see Crypt_GPG_Engine::$_agentProcess

Closes a the internal GPG - AGENT subprocess


private function _closePipe($pipeNumber)
    {
        $pipeNumber = intval($pipeNumber);
        if (array_key_exists($pipeNumber, $this->_openPipes)) {
            fflush($this->_openPipes[$pipeNumber]);
            fclose($this->_openPipes[$pipeNumber]);
            unset($this->_openPipes[$pipeNumber]);
        }
    }

private function _closePipe ( $ pipeNumber ) { $ pipeNumber = intval ( $ pipeNumber ) ; if ( array_key_exists ( $ pipeNumber , $ this -> _openPipes ) ) { fflush ( $ this -> _openPipes [ $ pipeNumber ] ) ; fclose ( $ this -> _openPipes [ $ pipeNumber ] ) ; unset ( $ this -> _openPipes [ $ pipeNumber ] ) ; } }

Closes an opened pipe used to communicate with the GPG subprocess

If the pipe is already closed, it is ignored. If the pipe is open, it
is flushed and then closed.

@param integer $pipeNumber the file descriptor number of the pipe to
close.

@return void


private function _closeIdleAgents()
    {
        if ($this->_gpgconf) {
            // before 2.1.13 --homedir wasn't supported, use env variable
            $env = array('GNUPGHOME' => $this->_homedir);
            $cmd = $this->_gpgconf . ' --kill gpg-agent';

            if ($process = proc_open($cmd, array(), $pipes, null, $env)) {
                proc_close($process);
            }
        }
    }

Forces automatically started gpg-agent process to cleanup and exit
within a minute.

This is needed in GnuPG 2.1 where agents are started
automatically by gpg process, not our code.

@return void

Forces automatically started gpg - agent process to cleanup and exit within a minute .


private function _findBinary($name)
    {
        $binary = '';

        if ($this->_isDarwin) {
            $locations = array(
                '/opt/local/bin/', // MacPorts
                '/usr/local/bin/', // Mac GPG
                '/sw/bin/',        // Fink
                '/usr/bin/'
            );
        } else {
            $locations = array(
                '/usr/bin/',
                '/usr/local/bin/'
            );
        }

        foreach ($locations as $location) {
            if (is_executable($location . $name)) {
                $binary = $location . $name;
                break;
            }
        }

        return $binary;
    }

private
function
_findBinary
(
$
name
)
{
$
binary
=
''
;
if
(
$
this
->
_isDarwin
)
{
$
locations
=
array
(
'/opt/local/bin/'
,
// MacPorts
'/usr/local/bin/'
,
// Mac GPG
'/sw/bin/'
,
// Fink
'/usr/bin/'
)
;
}
else
{
$
locations
=
array
(
'/usr/bin/'
,
'/usr/local/bin/'
)
;
}
foreach
(
$
locations
as
$
location
)
{
if
(
is_executable
(
$
location
.
$
name
)
)
{
$
binary
=
$
location
.
$
name
;
break
;
}
}
return
$
binary
;
}

Gets the location of a binary for the current operating system

@param string $name Name of a binary program

@return string The location of the binary for the current operating
system. If no suitable binary could be found, an empty
string is returned.


private function _getPinEntry()
    {
        // Find PinEntry program depending on the way how the package is installed
        $ds    = DIRECTORY_SEPARATOR;
        $root  = __DIR__ . $ds . '..' . $ds . '..' . $ds;
        $paths = array(
            '@bin-dir@', // PEAR
             $root . 'scripts', // Git
             $root . 'bin', // Composer
        );

        foreach ($paths as $path) {
            if (file_exists($path . $ds . 'crypt-gpg-pinentry')) {
                return $path . $ds . 'crypt-gpg-pinentry';
            }
        }
    }

Gets the location of the PinEntry script

@return string the location of the PinEntry script.


private function _debug($text)
    {
        if ($this->_debug) {
            if (php_sapi_name() === 'cli') {
                foreach (explode(PHP_EOL, $text) as $line) {
                    echo "Crypt_GPG DEBUG: ", $line, PHP_EOL;
                }
            } else if (is_callable($this->_debug)) {
                call_user_func($this->_debug, $text);
            } else {
                // running on a web server, format debug output nicely
                foreach (explode(PHP_EOL, $text) as $line) {
                    echo "Crypt_GPG DEBUG: ", htmlspecialchars($line),
                        '
', PHP_EOL;
                }
            }
        }
    }

Displays debug text if debugging is turned on

Debugging text is prepended with a debug identifier and echoed to stdout.

@param string $text the debugging text to display.

@return void


public function getPrimaryKey()
    {
        $primary_key = null;
        if (count($this->_subKeys) > 0) {
            $primary_key = $this->_subKeys[0];
        }
        return $primary_key;
    }

public function getPrimaryKey ( ) { $ primary_key = null ; if ( count ( $ this -> _subKeys ) > 0 ) { $ primary_key = $ this -> _subKeys [ 0 ] ; } return $ primary_key ; }

Gets the primary sub-key of this key

The primary key is the first added sub-key.

@return Crypt_GPG_SubKey the primary sub-key of this key.


public function canSign()
    {
        $canSign = false;
        foreach ($this->_subKeys as $subKey) {
            if ($subKey->canSign()) {
                $canSign = true;
                break;
            }
        }
        return $canSign;
    }

public function canSign ( ) { $ canSign = false ; foreach ( $ this -> _subKeys as $ subKey ) { if ( $ subKey -> canSign ( ) ) { $ canSign = true ; break ; } } return $ canSign ; }

Gets whether or not this key can sign data

This key can sign data if any sub-key of this key can sign data.

@return boolean true if this key can sign data and false if this key
cannot sign data.


public function canEncrypt()
    {
        $canEncrypt = false;
        foreach ($this->_subKeys as $subKey) {
            if ($subKey->canEncrypt()) {
                $canEncrypt = true;
                break;
            }
        }
        return $canEncrypt;
    }

public function canEncrypt ( ) { $ canEncrypt = false ; foreach ( $ this -> _subKeys as $ subKey ) { if ( $ subKey -> canEncrypt ( ) ) { $ canEncrypt = true ; break ; } } return $ canEncrypt ; }

Gets whether or not this key can encrypt data

This key can encrypt data if any sub-key of this key can encrypt data.

@return boolean true if this key can encrypt data and false if this
key cannot encrypt data.


public function setLogFilename($filename)
    {
        if (is_resource($this->logFile)) {
            fflush($this->logFile);
            fclose($this->logFile);
            $this->logFile = null;
        }

        if ($filename != '') {
            if (($this->logFile = fopen($filename, 'w')) === false) {
                $this->log(
                    'Unable to open log file "' . $filename . '" '
                    . 'for writing.' . PHP_EOL,
                    self::VERBOSITY_ERRORS
                );
                exit(1);
            } else {
                stream_set_write_buffer($this->logFile, 0);
            }
        }

        return $this;
    }

public
function
setLogFilename
(
$
filename
)
{
if
(
is_resource
(
$
this
->
logFile
)
)
{
fflush
(
$
this
->
logFile
)
;
fclose
(
$
this
->
logFile
)
;
$
this
->
logFile
=
;
}
if
(
$
filename
!=
''
)
{
if
(
(
$
this
->
logFile
=
fopen
(
$
filename
,
'w'
)
)
===
false
)
{
$
this
->
log
(
'Unable to open log file "'
.
$
filename
.
'" '
.
'for writing.'
.
PHP_EOL
,
self
::
VERBOSITY_ERRORS
)
;
exit
(
1
)
;
}
else
{
stream_set_write_buffer
(
$
this
->
logFile
,
0
)
;
}
}
return
$
this
;
}

Sets the log file location

@param string $filename the new log filename to use. If an empty string
is used, file-based logging is disabled.

@return Crypt_GPG_PinEntry the current object, for fluent interface.


protected function log($data, $level)
    {
        if ($this->verbosity >= $level) {
            if (is_resource($this->logFile)) {
                fwrite($this->logFile, $data);
                fflush($this->logFile);
            } else {
                $this->parser->outputter->stderr($data);
            }
        }

        return $this;
    }

protected function log ( $ data , $ level ) { if ( $ this -> verbosity >= $ level ) { if ( is_resource ( $ this -> logFile ) ) { fwrite ( $ this -> logFile , $ data ) ; fflush ( $ this -> logFile ) ; } else { $ this -> parser -> outputter -> stderr ( $ data ) ; } } return $ this ; }

Logs a message at the specified verbosity level

If a log file is used, the message is written to the log. Otherwise,
the message is sent to STDERR.

@param string  $data  the message to log.
@param integer $level the verbosity level above which the message should
be logged.

@return Crypt_GPG_PinEntry the current object, for fluent interface.


protected function connect()
    {
        // Binary operations will not work on Windows with PHP < 5.2.6.
        $rb = (version_compare(PHP_VERSION, '5.2.6') < 0) ? 'r' : 'rb';
        $wb = (version_compare(PHP_VERSION, '5.2.6') < 0) ? 'w' : 'wb';

        $this->stdin  = fopen('php://stdin', $rb);
        $this->stdout = fopen('php://stdout', $wb);

        if (function_exists('stream_set_read_buffer')) {
            stream_set_read_buffer($this->stdin, 0);
        }
        stream_set_write_buffer($this->stdout, 0);

        // initial handshake
        $this->send($this->getOK('Crypt_GPG pinentry ready and waiting'));

        return $this;
    }

Connects this pinentry to the assuan server

Opens I/O streams and sends initial handshake.

@return Crypt_GPG_PinEntry the current object, for fluent interface.


protected function parseCommand($line)
    {
        $this->log('<- ' . $line . PHP_EOL, self::VERBOSITY_ALL);

        $parts = explode(' ', $line, 2);

        $command = $parts[0];

        if (count($parts) === 2) {
            $data = $parts[1];
        } else {
            $data = null;
        }

        switch ($command) {
        case 'SETDESC':
            return $this->sendSetDescription($data);

        case 'MESSAGE':
            return $this->sendMessage();

        case 'CONFIRM':
            return $this->sendConfirm();

        case 'GETINFO':
            return $this->sendGetInfo($data);

        case 'GETPIN':
            return $this->sendGetPin($data);

        case 'RESET':
            return $this->sendReset();

        case 'BYE':
            return $this->sendBye();

        default:
            return $this->sendNotImplementedOK();
        }
    }

protected
function
parseCommand
(
$
line
)
{
$
this
->
log
(
'<- '
.
$
line
.
PHP_EOL
,
self
::
VERBOSITY_ALL
)
;
$
parts
=
explode
(
' '
,
$
line
,
2
)
;
$
command
=
$
parts
[
0
]
;
if
(
count
(
$
parts
)
===
2
)
{
$
data
=
$
parts
[
1
]
;
}
else
{
$
data
=
;
}
switch
(
$
command
)
{
case
'SETDESC'
:
return
$
this
->
sendSetDescription
(
$
data
)
;
case
'MESSAGE'
:
return
$
this
->
sendMessage
(
)
;
case
'CONFIRM'
:
return
$
this
->
sendConfirm
(
)
;
case
'GETINFO'
:
return
$
this
->
sendGetInfo
(
$
data
)
;
case
'GETPIN'
:
return
$
this
->
sendGetPin
(
$
data
)
;
case
'RESET'
:
return
$
this
->
sendReset
(
)
;
case
'BYE'
:
return
$
this
->
sendBye
(
)
;
default
:
return
$
this
->
sendNotImplementedOK
(
)
;
}
}

Parses an assuan command and performs the appropriate action

Documentation of the assuan commands for pinentry is limited to
non-existent. Most of these commands were taken from the C source code
to gpg-agent and pinentry.

Additional context was provided by using strace -f when calling the
gpg-agent.

@param string $line the assuan command line to parse

@return Crypt_GPG_PinEntry the current object, for fluent interface.


protected function initPinsFromENV()
    {
        if (($userData = getenv('PINENTRY_USER_DATA')) !== false) {
            $pins = json_decode($userData, true);
            if ($pins === null) {
                $this->log(
                    '-- failed to parse user data' . PHP_EOL,
                    self::VERBOSITY_ERRORS
                );
            } else {
                $this->pins = $pins;
                $this->log(
                    '-- got user data [not showing passphrases]' . PHP_EOL,
                    self::VERBOSITY_ALL
                );
            }
        }

        return $this;
    }

Initializes the PINs to be entered by this pinentry from the environment
variable PINENTRY_USER_DATA

The PINs are parsed from a JSON-encoded string.

@return Crypt_GPG_PinEntry the current object, for fluent interface.


protected function disconnect()
    {
        $this->log('-- disconnecting' . PHP_EOL, self::VERBOSITY_ALL);

        fflush($this->stdout);
        fclose($this->stdout);
        fclose($this->stdin);

        $this->stdin  = null;
        $this->stdout = null;

        $this->log('-- disconnected' . PHP_EOL, self::VERBOSITY_ALL);

        if (is_resource($this->logFile)) {
            fflush($this->logFile);
            fclose($this->logFile);
            $this->logFile = null;
        }

        return $this;
    }

protected
function
disconnect
(
)
{
$
this
->
log
(
'-- disconnecting'
.
PHP_EOL
,
self
::
VERBOSITY_ALL
)
;
fflush
(
$
this
->
stdout
)
;
fclose
(
$
this
->
stdout
)
;
fclose
(
$
this
->
stdin
)
;
$
this
->
stdin
=
;
$
this
->
stdout
=
;
$
this
->
log
(
'-- disconnected'
.
PHP_EOL
,
self
::
VERBOSITY_ALL
)
;
if
(
is_resource
(
$
this
->
logFile
)
)
{
fflush
(
$
this
->
logFile
)
;
fclose
(
$
this
->
logFile
)
;
$
this
->
logFile
=
;
}
return
$
this
;
}

Disconnects this pinentry from the Assuan server

@return Crypt_GPG_PinEntry the current object, for fluent interface.


protected function sendSetDescription($text)
    {
        $text = rawurldecode($text);
        $matches = array();
        // TODO: handle user id with quotation marks
        $exp = '/\n"(.+)"\n.*\sID ([A-Z0-9]+),\n/mu';
        if (preg_match($exp, $text, $matches) === 1) {
            $userId = $matches[1];
            $keyId  = $matches[2];

            if ($this->currentPin === null || $this->currentPin['keyId'] !== $keyId) {
                $this->currentPin = array(
                    'userId' => $userId,
                    'keyId'  => $keyId
                );
                $this->log(
                    '-- looking for PIN for ' . $keyId . PHP_EOL,
                    self::VERBOSITY_ALL
                );
            }
        }

        return $this->send($this->getOK());
    }

Parses the currently requested key identifier and user identifier from
the description passed to this pinentry

@param string $text the raw description sent from gpg-agent.

@return Crypt_GPG_PinEntry the current object, for fluent interface.


protected function sendGetPin()
    {
        $foundPin = '';

        if (is_array($this->currentPin)) {
            $keyIdLength = mb_strlen($this->currentPin['keyId'], '8bit');

            // search for the pin
            foreach ($this->pins as $_keyId => $pin) {
                // Warning: GnuPG 2.1 asks 3 times for passphrase if it is invalid
                $keyId        = $this->currentPin['keyId'];
                $_keyIdLength = mb_strlen($_keyId, '8bit');

                // Get last X characters of key identifier to compare
                // Most GnuPG versions use 8 characters, but recent ones can use 16,
                // We support 8 for backward compatibility
                if ($keyIdLength < $_keyIdLength) {
                    $_keyId = mb_substr($_keyId, -$keyIdLength, $keyIdLength, '8bit');
                } else if ($keyIdLength > $_keyIdLength) {
                    $keyId = mb_substr($keyId, -$_keyIdLength, $_keyIdLength, '8bit');
                }

                if ($_keyId === $keyId) {
                    $foundPin = $pin;
                    break;
                }
            }
        }

        return $this
            ->send($this->getData($foundPin))
            ->send($this->getOK());
    }

Sends the PIN value for the currently requested key

@return Crypt_GPG_PinEntry the current object, for fluent interface.


protected function sendGetInfo($data)
    {
        $parts   = explode(' ', $data, 2);
        $command = reset($parts);

        switch ($command) {
        case 'pid':
            return $this->sendGetInfoPID();
        default:
            return $this->send($this->getOK());
        }

        return $this;
    }

protected
function
sendGetInfo
(
$
data
)
{
$
parts
=
explode
(
' '
,
$
data
,
2
)
;
$
command
=
reset
(
$
parts
)
;
switch
(
$
command
)
{
case
'pid'
:
return
$
this
->
sendGetInfoPID
(
)
;
default
:
return
$
this
->
send
(
$
this
->
getOK
(
)
)
;
}
return
$
this
;
}

Sends information about this pinentry

@param string $data the information requested by the assuan server.
Currently only 'pid' is supported. Other requests
return no information.

@return Crypt_GPG_PinEntry the current object, for fluent interface.


protected function getData($data)
    {
        // Escape data. Only %, \n and \r need to be escaped but other
        // values are allowed to be escaped. See
        // http://www.gnupg.org/documentation/manuals/assuan/Server-responses.html
        $data = rawurlencode($data);
        $data = $this->getWordWrappedData($data, 'D');
        return $data;
    }

Gets data ready to send to the assuan server

Data is appropriately escaped and long lines are wrapped.

@param string $data the data to send to the assuan server.

@return string the properly escaped, formatted data.

@see http://www.gnupg.org/documentation/manuals/assuan/Server-responses.html


protected function getWordWrappedData($data, $prefix)
    {
        $lines = array();

        do {
            if (mb_strlen($data, '8bit') > 997) {
                $line = $prefix . ' ' . mb_strcut($data, 0, 996, 'utf-8') . "\\\n";
                $lines[] = $line;
                $lineLength = mb_strlen($line, '8bit') - 1;
                $dataLength = mb_substr($data, '8bit');
                $data = mb_substr(
                    $data,
                    $lineLength,
                    $dataLength - $lineLength,
                    '8bit'
                );
            } else {
                $lines[] = $prefix . ' ' . $data . "\n";
                $data = '';
            }
        } while ($data != '');

        return implode('', $lines);
    }

protected
function
getWordWrappedData
(
$
data
,
$
prefix
)
{
$
lines
=
array
(
)
;
do
{
if
(
mb_strlen
(
$
data
,
'8bit'
)
>
997
)
{
$
line
=
$
prefix
.
' '
.
mb_strcut
(
$
data
,
0
,
996
,
'utf-8'
)
.
"\\\n"
;
$
lines
[
]
=
$
line
;
$
lineLength
=
mb_strlen
(
$
line
,
'8bit'
)
1
;
$
dataLength
=
mb_substr
(
$
data
,
'8bit'
)
;
$
data
=
mb_substr
(
$
data
,
$
lineLength
,
$
dataLength
$
lineLength
,
'8bit'
)
;
}
else
{
$
lines
[
]
=
$
prefix
.
' '
.
$
data
.
"\n"
;
$
data
=
''
;
}
}
while
(
$
data
!=
''
)
;
return
implode
(
''
,
$
lines
)
;
}

Wraps strings at 1,000 bytes without splitting UTF-8 multibyte
characters

Each line is prepended with the specified line prefix. Wrapped lines
are automatically appended with \ characters.

Protocol strings are UTF-8 but maximum line length is 1,000 bytes.
<kbd>mb_strcut()</kbd> is used so we can limit line length by bytes
and not split characters across multiple lines.

@param string $data   the data to wrap.
@param string $prefix a single character to use as the line prefix. For
example, 'D' or '#'.

@return string the word-wrapped, prefixed string.

@see http://www.gnupg.org/documentation/manuals/assuan/Server-responses.html

Wraps strings at 1 000 bytes without splitting UTF - 8 multibyte characters


protected function send($data)
    {
        $this->log('-> ' . $data, self::VERBOSITY_ALL);
        fwrite($this->stdout, $data);
        fflush($this->stdout);
        return $this;
    }

protected
function
send
(
$
data
)
{
$
this
->
log
(
'-> '
.
$
data
,
self
::
VERBOSITY_ALL
)
;
fwrite
(
$
this
->
stdout
,
$
data
)
;
fflush
(
$
this
->
stdout
)
;
return
$
this
;
}

Sends raw data to the assuan server

@param string $data the data to send.

@return Crypt_GPG_PinEntry the current object, for fluent interface.


public static function buildMessages( Translations $translations ) {
		$plural_forms      = $translations->getPluralForms();
		$number_of_plurals = is_array( $plural_forms ) ? ( $plural_forms[0] - 1 ) : null;
		$messages          = [];
		$context_glue      = chr( 4 );

		foreach ( $translations as $translation ) {
			/** @var Translation $translation */

			if ( $translation->isDisabled() ) {
				continue;
			}

			$key = $translation->getOriginal();

			if ( $translation->hasContext() ) {
				$key = $translation->getContext() . $context_glue . $key;
			}

			if ( $translation->hasPluralTranslations( true ) ) {
				$message = $translation->getPluralTranslations( $number_of_plurals );
				array_unshift( $message, $translation->getTranslation() );
			} else {
				$message = [ $translation->getTranslation() ];
			}

			$messages[ $key ] = $message;
		}

		return $messages;
	}

public
static
function
buildMessages
(
Translations
$
translations
)
{
$
plural_forms
=
$
translations
->
getPluralForms
(
)
;
$
number_of_plurals
=
is_array
(
$
plural_forms
)
(
$
plural_forms
[
0
]
1
)
:
;
$
messages
=
[
]
;
$
context_glue
=
chr
(
4
)
;
foreach
(
$
translations
as
$
translation
)
{
/** @var Translation $translation */
if
(
$
translation
->
isDisabled
(
)
)
{
continue
;
}
$
key
=
$
translation
->
getOriginal
(
)
;
if
(
$
translation
->
hasContext
(
)
)
{
$
key
=
$
translation
->
getContext
(
)
.
$
context_glue
.
$
key
;
}
if
(
$
translation
->
hasPluralTranslations
(
true
)
)
{
$
message
=
$
translation
->
getPluralTranslations
(
$
number_of_plurals
)
;
array_unshift
(
$
message
,
$
translation
->
getTranslation
(
)
)
;
}
else
{
$
message
=
[
$
translation
->
getTranslation
(
)
]
;
}
$
messages
[
$
key
]
=
$
message
;
}
return
$
messages
;
}

Generates an array with all translations.

@param Translations $translations

@return array

Generates an array with all translations .


public static function setCommentBeforeHeaders( $comment ) {
		$comments = explode( "\n", $comment );

		foreach ( $comments as $line ) {
			if ( '' !== trim( $line ) ) {
				static::$comments_before_headers[] = '# ' . $line;
			}
		}
	}

public
static
function
setCommentBeforeHeaders
(
$
comment
)
{
$
comments
=
explode
(
"\n"
,
$
comment
)
;
foreach
(
$
comments
as
$
line
)
{
if
(
''
!==
trim
(
$
line
)
)
{
static
::
$
comments_before_headers
[
]
=
'# '
.
$
line
;
}
}
}

Text to include as a comment before the start of the PO contents

Doesn't need to include # in the beginning of lines, these are added automatically.

@param string $comment File comment.


private static function addLines( array &$lines, $name, $value ) {
		$newlines = self::multilineQuote( $value );

		if ( count( $newlines ) === 1 ) {
			$lines[] = $name . ' ' . $newlines[0];
		} else {
			$lines[] = $name . ' ""';

			foreach ( $newlines as $line ) {
				$lines[] = $line;
			}
		}
	}

private
static
function
addLines
(
array
&
$
lines
,
$
name
,
$
value
)
{
$
newlines
=
self
::
multilineQuote
(
$
value
)
;
if
(
count
(
$
newlines
)
===
1
)
{
$
lines
[
]
=
$
name
.
' '
.
$
newlines
[
0
]
;
}
else
{
$
lines
[
]
=
$
name
.
' ""'
;
foreach
(
$
newlines
as
$
line
)
{
$
lines
[
]
=
$
line
;
}
}
}

Add one or more lines depending whether the string is multiline or not.

@param array  &$lines Array lines should be added to.
@param string $name   Name of the line, e.g. msgstr or msgid_plural.
@param string $value  The line to add.

Add one or more lines depending whether the string is multiline or not .


protected function get_main_file_data() {
		$files = new IteratorIterator( new DirectoryIterator( $this->source ) );

		/** @var DirectoryIterator $file */
		foreach ( $files as $file ) {
			// wp-content/themes/my-theme/style.css
			if ( $file->isFile() && 'style' === $file->getBasename( '.css' ) && $file->isReadable() ) {
				$theme_data = static::get_file_data( $file->getRealPath(), array_combine( $this->get_file_headers( 'theme' ), $this->get_file_headers( 'theme' ) ) );

				// Stop when it contains a valid Theme Name header.
				if ( ! empty( $theme_data['Theme Name'] ) ) {
					WP_CLI::log( 'Theme stylesheet detected.' );
					WP_CLI::debug( sprintf( 'Theme stylesheet: %s', $file->getRealPath() ), 'make-pot' );

					$this->project_type = 'theme';

					return $theme_data;
				}
			}

			// wp-content/themes/my-themes/theme-a/style.css
			if ( $file->isDir() && ! $file->isDot() && is_readable( $file->getRealPath() . '/style.css' ) ) {
				$theme_data = static::get_file_data( $file->getRealPath() . '/style.css', array_combine( $this->get_file_headers( 'theme' ), $this->get_file_headers( 'theme' ) ) );

				// Stop when it contains a valid Theme Name header.
				if ( ! empty( $theme_data['Theme Name'] ) ) {
					WP_CLI::log( 'Theme stylesheet detected.' );
					WP_CLI::debug( sprintf( 'Theme stylesheet: %s', $file->getRealPath() . '/style.css' ), 'make-pot' );

					$this->project_type = 'theme';

					return $theme_data;
				}
			}

			// wp-content/plugins/my-plugin/my-plugin.php
			if ( $file->isFile() && $file->isReadable() && 'php' === $file->getExtension() ) {
				$plugin_data = static::get_file_data( $file->getRealPath(), array_combine( $this->get_file_headers( 'plugin' ), $this->get_file_headers( 'plugin' ) ) );

				// Stop when we find a file with a valid Plugin Name header.
				if ( ! empty( $plugin_data['Plugin Name'] ) ) {
					WP_CLI::log( 'Plugin file detected.' );
					WP_CLI::debug( sprintf( 'Plugin file: %s', $file->getRealPath() ), 'make-pot' );

					$this->project_type = 'plugin';

					return $plugin_data;
				}
			}
		}

		WP_CLI::debug( 'No valid theme stylesheet or plugin file found, treating as a regular project.', 'make-pot' );

		return [];
	}

Retrieves the main file data of the plugin or theme.

@return array

Retrieves the main file data of the plugin or theme .


protected function extract_strings() {
		$translations = new Translations();

		// Add existing strings first but don't keep headers.
		if ( ! empty( $this->merge ) ) {
			$existing_translations = new Translations();
			Po::fromFile( $this->merge, $existing_translations );
			$translations->mergeWith( $existing_translations, Merge::ADD | Merge::REMOVE );
		}

		PotGenerator::setCommentBeforeHeaders( $this->get_file_comment() );

		$this->set_default_headers( $translations );

		// POT files have no Language header.
		$translations->deleteHeader( Translations::HEADER_LANGUAGE );

		// Only relevant for PO files, not POT files.
		$translations->setHeader( 'PO-Revision-Date', 'YEAR-MO-DA HO:MI+ZONE' );

		if ( $this->domain ) {
			$translations->setDomain( $this->domain );
		}

		unset( $this->main_file_data['Version'], $this->main_file_data['License'], $this->main_file_data['Domain Path'], $this->main_file_data['Text Domain'] );

		// Set entries from main file data.
		foreach ( $this->main_file_data as $header => $data ) {
			if ( empty( $data ) ) {
				continue;
			}

			$translation = new Translation( '', $data );

			if ( isset( $this->main_file_data['Theme Name'] ) ) {
				$translation->addExtractedComment( sprintf( '%s of the theme', $header ) );
			} else {
				$translation->addExtractedComment( sprintf( '%s of the plugin', $header ) );
			}

			$translations[] = $translation;
		}

		try {
			$options = [
				// Extract 'Template Name' headers in theme files.
				'wpExtractTemplates' => isset( $this->main_file_data['Theme Name'] ),
				'include'            => $this->include,
				'exclude'            => $this->exclude,
				'extensions'         => [ 'php' ],
			];
			PhpCodeExtractor::fromDirectory( $this->source, $translations, $options );

			if ( ! $this->skip_js ) {
				JsCodeExtractor::fromDirectory(
					$this->source,
					$translations,
					[
						'include'    => $this->include,
						'exclude'    => $this->exclude,
						'extensions' => [ 'js' ],
					]
				);

				MapCodeExtractor::fromDirectory(
					$this->source,
					$translations,
					[
						'include'    => $this->include,
						'exclude'    => $this->exclude,
						'extensions' => [ 'map' ],
					]
				);
			}
		} catch ( \Exception $e ) {
			WP_CLI::error( $e->getMessage() );
		}

		foreach ( $this->exceptions as $translation ) {
			if ( $translations->find( $translation ) ) {
				unset( $translations[ $translation->getId() ] );
			}
		}

		if ( ! $this->skip_audit ) {
			$this->audit_strings( $translations );
		}

		return $translations;
	}

Creates a POT file and stores it on disk.

@throws WP_CLI\ExitException

@return Translations A Translation set.

Creates a POT file and stores it on disk .


protected function get_file_comment() {
		if ( '' === $this->file_comment ) {
			return '';
		}

		if ( isset( $this->file_comment ) ) {
			return implode( "\n", explode( '\n', $this->file_comment ) );
		}

		if ( isset( $this->main_file_data['Theme Name'] ) ) {
			if ( isset( $this->main_file_data['License'] ) ) {
				return sprintf(
					"Copyright (C) %1\$s %2\$s\nThis file is distributed under the %3\$s.",
					date( 'Y' ),
					$this->main_file_data['Author'],
					$this->main_file_data['License']
				);
			}

			return sprintf(
				"Copyright (C) %1\$s %2\$s\nThis file is distributed under the same license as the %3\$s theme.",
				date( 'Y' ),
				$this->main_file_data['Author'],
				$this->main_file_data['Theme Name']
			);
		}

		if ( isset( $this->main_file_data['Plugin Name'] ) ) {
			if ( isset( $this->main_file_data['License'] ) ) {
				return sprintf(
					"Copyright (C) %1\$s %2\$s\nThis file is distributed under the %3\$s.",
					date( 'Y' ),
					$this->main_file_data['Author'],
					$this->main_file_data['License']
				);
			}

			return sprintf(
				"Copyright (C) %1\$s %2\$s\nThis file is distributed under the same license as the %3\$s plugin.",
				date( 'Y' ),
				$this->main_file_data['Author'],
				$this->main_file_data['Plugin Name']
			);
		}

		return '';
	}

Returns the copyright comment for the given package.

@return string File comment.

Returns the copyright comment for the given package .


protected function set_default_headers( $translations ) {
		$name         = null;
		$version      = $this->get_wp_version();
		$bugs_address = null;

		if ( ! $version && isset( $this->main_file_data['Version'] ) ) {
			$version = $this->main_file_data['Version'];
		}

		if ( isset( $this->main_file_data['Theme Name'] ) ) {
			$name         = $this->main_file_data['Theme Name'];
			$bugs_address = sprintf( 'https://wordpress.org/support/theme/%s', $this->slug );
		} elseif ( isset( $this->main_file_data['Plugin Name'] ) ) {
			$name         = $this->main_file_data['Plugin Name'];
			$bugs_address = sprintf( 'https://wordpress.org/support/plugin/%s', $this->slug );
		}

		if ( null !== $this->package_name ) {
			$name = $this->package_name;
		}

		if ( null !== $name ) {
			$translations->setHeader( 'Project-Id-Version', $name . ( $version ? ' ' . $version : '' ) );
		}

		if ( null !== $bugs_address ) {
			$translations->setHeader( 'Report-Msgid-Bugs-To', $bugs_address );
		}

		$translations->setHeader( 'Last-Translator', 'FULL NAME <EMAIL@ADDRESS>' );
		$translations->setHeader( 'Language-Team', 'LANGUAGE <LL@li.org>' );
		$translations->setHeader( 'X-Generator', 'WP-CLI ' . WP_CLI_VERSION );

		foreach ( $this->headers as $key => $value ) {
			$translations->setHeader( $key, $value );
		}
	}

Sets default POT file headers for the project.

@param Translations $translations Translations object.

Sets default POT file headers for the project .


protected static function get_file_data( $file, $headers ) {
		// We don't need to write to the file, so just open for reading.
		$fp = fopen( $file, 'rb' );

		// Pull only the first 8kiB of the file in.
		$file_data = fread( $fp, 8192 );

		// PHP will close file handle, but we are good citizens.
		fclose( $fp );

		// Make sure we catch CR-only line endings.
		$file_data = str_replace( "\r", "\n", $file_data );

		return static::get_file_data_from_string( $file_data, $headers );
	}

Retrieves metadata from a file.

Searches for metadata in the first 8kiB of a file, such as a plugin or theme.
Each piece of metadata must be on its own line. Fields can not span multiple
lines, the value will get cut at the end of the first line.

If the file data is not within that first 8kiB, then the author should correct
their plugin file and move the data headers to the top.

@see get_file_data()

@param string $file Path to the file.
@param array $headers List of headers, in the format array('HeaderKey' => 'Header Name').

@return array Array of file headers in `HeaderKey => Header Value` format.


public static function get_file_data_from_string( $string, $headers ) {
		foreach ( $headers as $field => $regex ) {
			if ( preg_match( '/^[ \t\/*#@]*' . preg_quote( $regex, '/' ) . ':(.*)$/mi', $string, $match ) && $match[1] ) {
				$headers[ $field ] = static::_cleanup_header_comment( $match[1] );
			} else {
				$headers[ $field ] = '';
			}
		}

		return $headers;
	}

public
static
function
get_file_data_from_string
(
$
string
,
$
headers
)
{
foreach
(
$
headers
as
$
field
=>
$
regex
)
{
if
(
preg_match
(
'/^[ \t\/*#@]*'
.
preg_quote
(
$
regex
,
'/'
)
.
':(.*)$/mi'
,
$
string
,
$
match
)
&&
$
match
[
1
]
)
{
$
headers
[
$
field
]
=
static
::
_cleanup_header_comment
(
$
match
[
1
]
)
;
}
else
{
$
headers
[
$
field
]
=
''
;
}
}
return
$
headers
;
}

Retrieves metadata from a string.

@param string $string String to look for metadata in.
@param array $headers List of headers.

@return array Array of file headers in `HeaderKey => Header Value` format.


private function resolveExpressionCallee( Node\CallExpression $node ) {
		$callee = $node->getCallee();

		// If the callee is a simple identifier it can simply be returned.
		// For example: __( "translation" ).
		if ( 'Identifier' === $callee->getType() ) {
			return [
				'name'     => $callee->getName(),
				'comments' => $callee->getLeadingComments(),
			];
		}

		// If the callee is a member expression resolve it to the property.
		// For example: wp.i18n.__( "translation" ) or u.__( "translation" ).
		if (
			'MemberExpression' === $callee->getType() &&
			'Identifier' === $callee->getProperty()->getType()
		) {
			// Make sure to unpack wp.i18n which is a nested MemberExpression.
			$comments = 'MemberExpression' === $callee->getObject()->getType()
				? $callee->getObject()->getObject()->getLeadingComments()
				: $callee->getObject()->getLeadingComments();

			return [
				'name'     => $callee->getProperty()->getName(),
				'comments' => $comments,
			];
		}

		// If the callee is a call expression as created by Webpack resolve it.
		// For example: Object(u.__)( "translation" ).
		if (
			'CallExpression' === $callee->getType() &&
			'Identifier' === $callee->getCallee()->getType() &&
			'Object' === $callee->getCallee()->getName() &&
			[] !== $callee->getArguments() &&
			'MemberExpression' === $callee->getArguments()[0]->getType()
		) {
			$property = $callee->getArguments()[0]->getProperty();

			// Matches minified webpack statements: Object(u.__)( "translation" ).
			if ( 'Identifier' === $property->getType() ) {
				return [
					'name'     => $property->getName(),
					'comments' => $callee->getCallee()->getLeadingComments(),
				];
			}

			// Matches unminified webpack statements:
			// Object(_wordpress_i18n__WEBPACK_IMPORTED_MODULE_7__["__"])( "translation" );
			if ( 'Literal' === $property->getType() ) {
				return [
					'name'     => $property->getValue(),
					'comments' => $callee->getCallee()->getLeadingComments(),
				];
			}
		}

		// Unknown format.
		return false;
	}

Resolve the callee of a call expression using known formats.

@param Node\CallExpression $node The call expression whose callee to resolve.

@return array|bool Array containing the name and comments of the identifier if resolved. False if not.

Resolve the callee of a call expression using known formats .


private function commentPrecedesNode( Node\Comment $comment, Node\Node $node ) {
		// Comments should be on the same or an earlier line than the translation.
		if ( $node->getLocation()->getStart()->getLine() - $comment->getLocation()->getEnd()->getLine() > 1 ) {
			return false;
		}

		// Comments on the same line should be before the translation.
		if (
			$node->getLocation()->getStart()->getLine() === $comment->getLocation()->getEnd()->getLine() &&
			$node->getLocation()->getStart()->getColumn() < $comment->getLocation()->getStart()->getColumn()
		) {
			return false;
		}

		return true;
	}

Returns wether or not a comment precedes a node.
The comment must be before the node and on the same line or the one before.

@param Node\Comment $comment The comment.
@param Node\Node    $node    The node.

@return bool Whether or not the comment precedes the node.

Returns wether or not a comment precedes a node . The comment must be before the node and on the same line or the one before .


protected static function calculateMatchScore( SplFileInfo $file, array $matchers = [] ) {
		if ( empty( $matchers ) ) {
			return 0;
		}

		if ( in_array( $file->getBasename(), $matchers, true ) ) {
			return 10;
		}

		// Check for more complex paths, e.g. /some/sub/folder.
		$root_relative_path = str_replace( static::$dir, '', $file->getPathname() );

		foreach ( $matchers as $path_or_file ) {
			$pattern = preg_quote( str_replace( '*', '__wildcard__', $path_or_file ), '/' );
			$pattern = '(^|/)' . str_replace( '__wildcard__', '(.+)', $pattern );

			// Base score is the amount of nested directories, discounting wildcards.
			$base_score = count(
				array_filter(
					explode( '/', $path_or_file ),
					function ( $component ) {
						return '*' !== $component;
					}
				)
			);
			if ( 0 === $base_score ) {
				// If the matcher is simply * it gets a score above the implicit score but below 1.
				$base_score = 0.2;
			}

			// If the matcher contains no wildcards and matches the end of the path.
			if (
				false === strpos( $path_or_file, '*' ) &&
				false !== mb_ereg( $pattern . '$', $root_relative_path )
			) {
				return $base_score * 10;
			}

			// If the matcher matches the end of the path or a full directory contained.
			if ( false !== mb_ereg( $pattern . '(/|$)', $root_relative_path ) ) {
				return $base_score;
			}
		}

		return 0;
	}

Determines whether a file is valid based on given matchers.

@param SplFileInfo $file     File or directory.
@param array       $matchers List of files and directories to match.
@return int How strongly the file is matched.

Determines whether a file is valid based on given matchers .


protected static function containsMatchingChildren( SplFileInfo $dir, array $matchers = [] ) {
		if ( empty( $matchers ) ) {
			return false;
		}

		/** @var string $root_relative_path */
		$root_relative_path = str_replace( static::$dir, '', $dir->getPathname() );

		foreach ( $matchers as $path_or_file ) {
			// If the matcher contains no wildcards and the path matches the start of the matcher.
			if (
				'' !== $root_relative_path &&
				false === strpos( $path_or_file, '*' ) &&
				0 === strpos( $path_or_file . '/', $root_relative_path )
			) {
				return true;
			}

			$base = current( explode( '*', $path_or_file ) );

			// If start of the path matches the start of the matcher until the first wildcard.
			// Or the start of the matcher until the first wildcard matches the start of the path.
			if (
				( '' !== $root_relative_path && 0 === strpos( $base, $root_relative_path ) ) ||
				0 === strpos( $root_relative_path, $base )
			) {
				return true;
			}
		}

		return false;
	}

Determines whether or not a directory has children that may be matched.

@param SplFileInfo $dir      Directory.
@param array       $matchers List of files and directories to match.
@return bool Whether or not there are any matchers for children of this directory.

Determines whether or not a directory has children that may be matched .


public static function getFilesFromDirectory( $dir, array $include = [], array $exclude = [], $extensions = [] ) {
		$filtered_files = [];

		$files = new RecursiveIteratorIterator(
			new RecursiveCallbackFilterIterator(
				new RecursiveDirectoryIterator( $dir, RecursiveDirectoryIterator::SKIP_DOTS | RecursiveDirectoryIterator::UNIX_PATHS ),
				function ( $file, $key, $iterator ) use ( $include, $exclude, $extensions ) {
					/** @var RecursiveCallbackFilterIterator $iterator */
					/** @var SplFileInfo $file */

					// If no $include is passed everything gets the weakest possible matching score.
					$inclusion_score = empty( $include ) ? 0.1 : static::calculateMatchScore( $file, $include );
					$exclusion_score = static::calculateMatchScore( $file, $exclude );

					// Always include directories that aren't excluded.
					if ( 0 === $exclusion_score && $iterator->hasChildren() ) {
						return true;
					}

					if ( 0 === $inclusion_score || $exclusion_score > $inclusion_score ) {
						// Always include directories that may have matching children even if they are excluded.
						return $iterator->hasChildren() && static::containsMatchingChildren( $file, $include );
					}

					return ( $file->isFile() && in_array( $file->getExtension(), $extensions, true ) );
				}
			),
			RecursiveIteratorIterator::CHILD_FIRST
		);

		foreach ( $files as $file ) {
			/** @var SplFileInfo $file */
			if ( ! $file->isFile() || ! in_array( $file->getExtension(), $extensions, true ) ) {
				continue;
			}

			$filtered_files[] = Utils\normalize_path( $file->getPathname() );
		}

		return $filtered_files;
	}

Recursively gets all PHP files within a directory.

@param string $dir A path of a directory.
@param array $include List of files and directories to include.
@param array $exclude List of files and directories to skip.
@param array $extensions List of filename extensions to process.

@return array File list.

Recursively gets all PHP files within a directory .


protected function make_json( $source_file, $destination ) {
		/** @var Translations[] $mapping */
		$mapping      = [];
		$translations = new Translations();
		$result       = [];

		PoExtractor::fromFile( $source_file, $translations );

		$base_file_name = basename( $source_file, '.po' );

		foreach ( $translations as $index => $translation ) {
			/** @var Translation $translation */

			// Find all unique sources this translation originates from.
			$sources = array_map(
				function ( $reference ) {
					$file = $reference[0];

					if ( substr( $file, - 7 ) === '.min.js' ) {
						return substr( $file, 0, - 7 ) . '.js';
					}

					if ( substr( $file, - 3 ) === '.js' ) {
						return $file;
					}

					return null;
				},
				$translation->getReferences()
			);

			$sources = array_unique( array_filter( $sources ) );

			foreach ( $sources as $source ) {
				if ( ! isset( $mapping[ $source ] ) ) {
					$mapping[ $source ] = new Translations();

					// phpcs:ignore Squiz.PHP.CommentedOutCode.Found -- Provide code that is meant to be used once the bug is fixed.
					// See https://core.trac.wordpress.org/ticket/45441
					// $mapping[ $source ]->setDomain( $translations->getDomain() );

					$mapping[ $source ]->setHeader( 'Language', $translations->getLanguage() );
					$mapping[ $source ]->setHeader( 'PO-Revision-Date', $translations->getHeader( 'PO-Revision-Date' ) );

					$plural_forms = $translations->getPluralForms();

					if ( $plural_forms ) {
						list( $count, $rule ) = $plural_forms;
						$mapping[ $source ]->setPluralForms( $count, $rule );
					}
				}

				$mapping[ $source ][] = $translation;
			}
		}

		$result += $this->build_json_files( $mapping, $base_file_name, $destination );

		return $result;
	}

Splits a single PO file into multiple JSON files.

@param string $source_file Path to the source file.
@param string $destination Path to the destination directory.
@return array List of created JSON files.

Splits a single PO file into multiple JSON files .


protected function build_json_files( $mapping, $base_file_name, $destination ) {
		$result = [];

		foreach ( $mapping as $file => $translations ) {
			/** @var Translations $translations */

			$hash             = md5( $file );
			$destination_file = "${destination}/{$base_file_name}-{$hash}.json";

			$success = JedGenerator::toFile(
				$translations,
				$destination_file,
				[
					'json'   => $this->json_options,
					'source' => $file,
				]
			);

			if ( ! $success ) {
				WP_CLI::warning( sprintf( 'Could not create file %s', basename( $destination_file, '.json' ) ) );

				continue;
			}

			$result[] = $destination_file;
		}

		return $result;
	}

Builds a mapping of JS file names to translation entries.

Exports translations for each JS file to a separate translation file.

@param array  $mapping        A mapping of files to translation entries.
@param string $base_file_name Base file name for JSON files.
@param string $destination    Path to the destination directory.

@return array List of created JSON files.

Builds a mapping of JS file names to translation entries .


protected function remove_js_strings_from_po_file( $source_file ) {
		/** @var Translations[] $mapping */
		$translations = new Translations();

		PoExtractor::fromFile( $source_file, $translations );

		foreach ( $translations->getArrayCopy() as $translation ) {
			/** @var Translation $translation */

			if ( ! $translation->hasReferences() ) {
				continue;
			}

			foreach ( $translation->getReferences() as $reference ) {
				$file = $reference[0];

				if ( substr( $file, - 3 ) !== '.js' ) {
					continue 2;
				}
			}

			unset( $translations[ $translation->getId() ] );
		}

		return PoGenerator::toFile( $translations, $source_file );
	}

protected
function
remove_js_strings_from_po_file
(
$
source_file
)
{
/** @var Translations[] $mapping */
$
translations
=
new
Translations
(
)
;
PoExtractor
::
fromFile
(
$
source_file
,
$
translations
)
;
foreach
(
$
translations
->
getArrayCopy
(
)
as
$
translation
)
{
/** @var Translation $translation */
if
(
!
$
translation
->
hasReferences
(
)
)
{
continue
;
}
foreach
(
$
translation
->
getReferences
(
)
as
$
reference
)
{
$
file
=
$
reference
[
0
]
;
if
(
substr
(
$
file
,
3
)
!==
'.js'
)
{
continue
2
;
}
}
unset
(
$
translations
[
$
translation
->
getId
(
)
]
)
;
}
return
PoGenerator
::
toFile
(
$
translations
,
$
source_file
)
;
}

Removes strings from PO file that only occur in JavaScript file.

@param string $source_file Path to the PO file.
@return bool True on success, false otherwise.

Removes strings from PO file that only occur in JavaScript file .


public function markUpdatesSuccessful(array $names, $checkListPoints = TRUE) {
    if ($this->updateChecklist === FALSE) {
      return;
    }
    $this->setSuccessfulByHook($names, TRUE);
    if ($checkListPoints) {
      $this->checkListPoints($names);
    }
  }

public function markUpdatesSuccessful ( array $ names , $ checkListPoints = TRUE ) { if ( $ this -> updateChecklist === FALSE ) { return ; } $ this -> setSuccessfulByHook ( $ names , TRUE ) ; if ( $ checkListPoints ) { $ this -> checkListPoints ( $ names ) ; } }

Marks a list of updates as successful.

@param array $names
Array of update ids.
@param bool $checkListPoints
Indicates the corresponding checkbox should be checked.

@throws \Drupal\Core\Entity\EntityStorageException


public function markAllUpdates($status = TRUE) {
    if ($this->updateChecklist === FALSE) {
      return;
    }

    $keys = [];
    foreach ($this->updateChecklist->items as $versionItems) {
      foreach ($versionItems as $key => $item) {
        if (is_array($item)) {
          $keys[] = $key;
        }
      }
    }

    $this->setSuccessfulByHook($keys, $status);
    $this->checkAllListPoints($status);
  }

public function markAllUpdates ( $ status = TRUE ) { if ( $ this -> updateChecklist === FALSE ) { return ; } $ keys = [ ] ; foreach ( $ this -> updateChecklist -> items as $ versionItems ) { foreach ( $ versionItems as $ key => $ item ) { if ( is_array ( $ item ) ) { $ keys [ ] = $ key ; } } } $ this -> setSuccessfulByHook ( $ keys , $ status ) ; $ this -> checkAllListPoints ( $ status ) ; }

Marks a list of updates.

@param bool $status
Checkboxes enabled or disabled.

@throws \Drupal\Core\Entity\EntityStorageException


protected function setSuccessfulByHook(array $keys, $status = TRUE) {
    foreach ($keys as $key) {
      if ($update = Update::load($key)) {
        $update->setSuccessfulByHook($status)->save();
      }
      else {
        Update::create(
          [
            'id' => $key,
            'successful_by_hook' => $status,
          ]
        )->save();
      }
    }
  }

protected function setSuccessfulByHook ( array $ keys , $ status = TRUE ) { foreach ( $ keys as $ key ) { if ( $ update = Update :: load ( $ key ) ) { $ update -> setSuccessfulByHook ( $ status ) -> save ( ) ; } else { Update :: create ( [ 'id' => $ key , 'successful_by_hook' => $ status , ] ) -> save ( ) ; } } }

Set status for update keys.

@param array $keys
Keys for update entries.
@param bool $status
Status that should be set.

@throws \Drupal\Core\Entity\EntityStorageException


protected function checkListPoints(array $names) {

    /* @var \Drupal\Core\Config\Config $drooplerUpdateConfig */
    $drooplerUpdateConfig = $this->configFactory->getEditable('checklistapi.progress.d_update');
    $user = $this->account->id();
    $time = time();
    foreach ($names as $name) {
      if ($drooplerUpdateConfig&& !$drooplerUpdateConfig->get(ChecklistapiChecklist::PROGRESS_CONFIG_KEY . ".#items.$name")) {
        $drooplerUpdateConfig
          ->set(ChecklistapiChecklist::PROGRESS_CONFIG_KEY . ".#items.$name", [
            '#completed' => time(),
            '#uid' => $user,
          ]);
      }
    }
    $drooplerUpdateConfig
      ->set(ChecklistapiChecklist::PROGRESS_CONFIG_KEY . '.#completed_items', count($drooplerUpdateConfig->get(ChecklistapiChecklist::PROGRESS_CONFIG_KEY . ".#items")))
      ->set(ChecklistapiChecklist::PROGRESS_CONFIG_KEY . '.#changed', $time)
      ->set(ChecklistapiChecklist::PROGRESS_CONFIG_KEY . '.#changed_by', $user)
      ->save();
  }

Checks an array of bulletpoints on a checklist.

@param array $names
Array of the bulletpoints.

Checks an array of bulletpoints on a checklist .


protected function checkAllListPoints($status = TRUE) {
    $drooplerUpdateConfig = $this->configFactory
      ->getEditable('checklistapi.progress.d_update');
    $user = $this->account->id();
    $time = time();
    $drooplerUpdateConfig
      ->set(ChecklistapiChecklist::PROGRESS_CONFIG_KEY . '.#changed', $time)
      ->set(ChecklistapiChecklist::PROGRESS_CONFIG_KEY . '.#changed_by', $user);
    $exclude = [
      '#title',
      '#description',
      '#weight',
    ];
    foreach ($this->updateChecklist->items as $versionItems) {
      foreach ($versionItems as $itemName => $item) {
        if (!in_array($itemName, $exclude)) {
          if ($status) {
            $drooplerUpdateConfig
              ->set(ChecklistapiChecklist::PROGRESS_CONFIG_KEY . ".#items.$itemName", [
                '#completed' => $time,
                '#uid' => $user,
              ]);
          }
          else {
            $drooplerUpdateConfig
              ->clear(ChecklistapiChecklist::PROGRESS_CONFIG_KEY . ".#items.$itemName");
          }
        }
      }
    }
    $drooplerUpdateConfig
      ->set(ChecklistapiChecklist::PROGRESS_CONFIG_KEY . '.#completed_items', count($drooplerUpdateConfig->get(ChecklistapiChecklist::PROGRESS_CONFIG_KEY . ".#items")))
      ->save();
  }

protected function checkAllListPoints ( $ status = TRUE ) { $ drooplerUpdateConfig = $ this -> configFactory -> getEditable ( 'checklistapi.progress.d_update' ) ; $ user = $ this -> account -> id ( ) ; $ time = time ( ) ; $ drooplerUpdateConfig -> set ( ChecklistapiChecklist :: PROGRESS_CONFIG_KEY . '.#changed' , $ time ) -> set ( ChecklistapiChecklist :: PROGRESS_CONFIG_KEY . '.#changed_by' , $ user ) ; $ exclude = [ '#title' , '#description' , '#weight' , ] ; foreach ( $ this -> updateChecklist -> items as $ versionItems ) { foreach ( $ versionItems as $ itemName => $ item ) { if ( ! in_array ( $ itemName , $ exclude ) ) { if ( $ status ) { $ drooplerUpdateConfig -> set ( ChecklistapiChecklist :: PROGRESS_CONFIG_KEY . ".#items.$itemName" , [ '#completed' => $ time , '#uid' => $ user , ] ) ; } else { $ drooplerUpdateConfig -> clear ( ChecklistapiChecklist :: PROGRESS_CONFIG_KEY . ".#items.$itemName" ) ; } } } } $ drooplerUpdateConfig -> set ( ChecklistapiChecklist :: PROGRESS_CONFIG_KEY . '.#completed_items' , count ( $ drooplerUpdateConfig -> get ( ChecklistapiChecklist :: PROGRESS_CONFIG_KEY . ".#items" ) ) ) -> save ( ) ; }

Checks all the bulletpoints on a checklist.

@param bool $status
Checkboxes enabled or disabled.

Checks all the bulletpoints on a checklist .


public function saveProgress(array $values) {
    $user = \Drupal::currentUser();

    $time = time();
    $num_changed_items = 0;
    $progress = [
      '#changed' => $time,
      '#changed_by' => $user->id(),
      '#completed_items' => 0,
      '#items' => [],
    ];
    $status = [
      'positive' => [],
      'negative' => [],
    ];

    $drooplerUpdateConfig = $this->configFactory->getEditable('checklistapi.progress.d_update');
    $savedProgress = $drooplerUpdateConfig->get(ChecklistapiChecklist::PROGRESS_CONFIG_KEY);

    foreach ($values as $group_key => $group) {
      foreach ($group as $item_key => $item) {
        $old_item = (!empty($savedProgress['#items'][$item_key])) ? $savedProgress['#items'][$item_key] : 0;
        if ($item) {
          // Item is checked.
          $status['positive'][] = $item_key;
          $progress['#completed_items']++;
          if ($old_item) {
            // Item was previously checked. Use saved value.
            $new_item = $old_item;
          }
          else {
            // Item is newly checked. Set new value.
            $new_item = [
              '#completed' => $time,
              '#uid' => $user->id(),
            ];
            $num_changed_items++;
          }
          $progress['#items'][$item_key] = $new_item;
        }
        else {
          // Item is unchecked.
          $status['negative'][] = $item_key;
          if ($old_item) {
            // Item was previously checked.
            $num_changed_items++;
          }
        }
      }
    }

    $this->setSuccessfulByHook($status['positive'], TRUE);
    $this->setSuccessfulByHook($status['negative'], FALSE);

    ksort($progress);

    $drooplerUpdateConfig->set(ChecklistapiChecklist::PROGRESS_CONFIG_KEY, $progress)->save();
    drupal_set_message(\Drupal::translation()->formatPlural(
      $num_changed_items,
      '%title progress has been saved. 1 item changed.',
      '%title progress has been saved. @count items changed.',
      ['%title' => $this->updateChecklist->title]
    ));
  }

Updates and saves progress of the update checklist.

@param array $values
Two dimensional array with structure "version_key" => ["checkbox_id" => TRUE|FALSE]

@throws \Drupal\Core\Entity\EntityStorageException

Updates and saves progress of the update checklist .


public function render() {
    $output = '
' . t('Droopler is a Drupal 8 profile designed to kickstart a new webpage in a few minutes') . '
';
    $output .= '
' . t('More info about Droopler - See official Droopler website.', [':link' => 'https://droopler.com/']) . '
';
    $output .= '
' . t('Support') . '
';
    $output .= '
' . t('Do You need support with Droopler? -  Droptica.com.', [':link' => 'https://droptica.com']) . '
';
    $output .= '
' . t('Github') . '
';
    $output .= '
' . t('https://github.com/droptica/droopler_project - Boilerplate for new projects based on Droopler. If you wish to use Droopler - fork (or download) this repository. It contains a minimum set of code to start your new website.', [':link' => 'https://github.com/droptica/droopler_project']) . '
';
    $output .= '
' . t('https://github.com/droptica/droopler - This is Drupal installation profile.', [':link' => 'https://github.com/droptica/droopler']) . '
';
    return [
      '#type' => 'markup',
      '#markup' => '
' . $output . '
',
    ];
  }

Prints a page about Droopler distribution.

@return array
A render array for the info page.

Prints a page about Droopler distribution .


public function generateHashFromDatabase($configName) {
    $config = \Drupal::config($configName)->getRawData();
    if (empty($config)) {
      return FALSE;
    }

    unset($config['uuid']);
    unset($config['lang']);
    unset($config['langcode']);
    $configString = serialize($config);

    return md5($configString);
  }

public function generateHashFromDatabase ( $ configName ) { $ config = \ Drupal :: config ( $ configName ) -> getRawData ( ) ; if ( empty ( $ config ) ) { return FALSE ; } unset ( $ config [ 'uuid' ] ) ; unset ( $ config [ 'lang' ] ) ; unset ( $ config [ 'langcode' ] ) ; $ configString = serialize ( $ config ) ; return md5 ( $ configString ) ; }

Generates hash for the specified config.

@param string $configName
Full name of the config, eg node.type.content_page.

@return bool|string
Returns hash or false if there is no config with provided name.

Generates hash for the specified config .


public function compare($configName, $hash = NULL) {
    if (empty($hash)) {
      return TRUE;
    }
    else {
      return $this->generateHashFromDatabase($configName) == $hash;
    }
  }

public function compare ( $ configName , $ hash = NULL ) { if ( empty ( $ hash ) ) { return TRUE ; } else { return $ this -> generateHashFromDatabase ( $ configName ) == $ hash ; } }

Compares config name hash wit provided hash.

@param string $configName
Full name of the config, eg node.type.content_page.
@param string $hash
Optional argument with hash.

@return bool
Returns true if hashes are the same or hash was not provided, false on different hashes.

Compares config name hash wit provided hash .


public function importConfig($module, $name, $hash) {
    $configPath = drupal_get_path('module', $module) . '/config/install';
    $source = new FileStorage($configPath);
    $data = $source->read($name);
    if (!$data || !$this->configManager->compare($name, $hash)) {
      return false;
    }

    return $this->configStorage->write($name, $data);
  }

public function importConfig ( $ module , $ name , $ hash ) { $ configPath = drupal_get_path ( 'module' , $ module ) . '/config/install' ; $ source = new FileStorage ( $ configPath ) ; $ data = $ source -> read ( $ name ) ; if ( ! $ data || ! $ this -> configManager -> compare ( $ name , $ hash ) ) { return false ; } return $ this -> configStorage -> write ( $ name , $ data ) ; }

Import a config file.

@param string $module
Module name.
@param string $name
Config file name without .yml extension.
@param string $hash
Hashed array with config data.

@return bool
Returns if config was imported successfully.


public function importConfigs(array $configs) {
    $status = [];
    foreach ($configs as $module => $config) {
      foreach ($config as $configName => $configHash) {
        $status[] = $this->importConfig($module, $configName, $configHash);
      }
    }

    return !in_array(false, $status);
  }

public function importConfigs ( array $ configs ) { $ status = [ ] ; foreach ( $ configs as $ module => $ config ) { foreach ( $ config as $ configName => $ configHash ) { $ status [ ] = $ this -> importConfig ( $ module , $ configName , $ configHash ) ; } } return ! in_array ( false , $ status ) ; }

Import many config files at once.

@param array $configs
Two dimensional array with structure "module_name" => ["config_file_name" => "config_hash"]

@return bool
Returns if all of the configs were imported successfully.


public function installModules(array $modules, $enableDependencies = TRUE) {
    if (empty($modules) || !is_array($modules)) {
      return FALSE;
    }

    $moduleData = system_rebuild_module_data();
    $modules = array_combine($modules, $modules);
    if ($missing_modules = array_diff_key($modules, $moduleData)) {
      return FALSE;
    }

    return $this->moduleInstaller->install($modules, $enableDependencies);
  }

public function installModules ( array $ modules , $ enableDependencies = TRUE ) { if ( empty ( $ modules ) || ! is_array ( $ modules ) ) { return FALSE ; } $ moduleData = system_rebuild_module_data ( ) ; $ modules = array_combine ( $ modules , $ modules ) ; if ( $ missing_modules = array_diff_key ( $ modules , $ moduleData ) ) { return FALSE ; } return $ this -> moduleInstaller -> install ( $ modules , $ enableDependencies ) ; }

Install modules.

@param array $modules
Numeric array with module names.
@param bool $enableDependencies
Should dependencies for modules be enabled.

@return bool
Returns if modules were installed successfully.

@throws \Drupal\Core\Extension\MissingDependencyException


public function checkLink($link_hash, $paragraph_id) {
    // Load file and paragraph.
    $entity = $this->getSubscribeFileEntity('link_hash', $link_hash);
    $this->checkLinkActive($entity);
    $paragraph = Paragraph::load($paragraph_id);
    $file_hash = $entity->get('file_hash')->get(0)->getValue();
    $link_options = [
      'absolute' => TRUE,
      'attributes' => ['class' => 'btn btn-primary btn-orange']
    ];

    // Generate download link/
    $button_text = $paragraph->get('field_d_p_sf_download_button')->getValue();
    $download_link = Link::createFromRoute($button_text[0]['value'], 'd_p_subscribe_file.downloadfile.getFile', ['file_hash' => $file_hash['value']], $link_options);
    $rendered_download_link = $download_link->toString()->getGeneratedLink();

    // Generate download page with link.
    $display_settings = ['label' => 'hidden',];
    $body = $paragraph->get('field_d_p_sf_download_page')->view($display_settings);
    $body[0]['#text'] = str_replace("[download-button]", $rendered_download_link, $body[0]['#text']);
    return [
      '#theme' => 'd_p_subscribe_file_download_page',
      '#body' => $body,
    ];
  }

Check link to download file.

@param string $link_hash
Link Hash.
@param string $paragraph_id
Paragraph id.

@return array
Render download page.


private function checkLinkActive($entity) {
    $created = $entity->get('created')->get(0)->getValue();
    if (time() > $created['value'] + 86400) {
      $this->goHomeWithMessage(t('Link is not active, please add your email again'));
    }
  }

Checking link was created before 24h.
@param $entity


private function goHomeWithMessage($message) {
    drupal_set_message($message);
    $url = Url::fromRoute('<front>');
    $response = new RedirectResponse($url->toString());
    $response->send();
  }

private function goHomeWithMessage ( $ message ) { drupal_set_message ( $ message ) ; $ url = Url :: fromRoute ( '<front>' ) ; $ response = new RedirectResponse ( $ url -> toString ( ) ) ; $ response -> send ( ) ; }

Redirect to home page and show drupal message
@param $message


public function getFile($file_hash) {
    $entity = $this->getSubscribeFileEntity('file_hash', $file_hash);
    $this->checkLinkActive($entity);
    $file = File::load($entity->get('fid')->getValue()[0]['value']);
    $uri = $file->getFileUri();
    $response = new BinaryFileResponse($uri);
    $response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT);
    return $response;
  }

public function getFile ( $ file_hash ) { $ entity = $ this -> getSubscribeFileEntity ( 'file_hash' , $ file_hash ) ; $ this -> checkLinkActive ( $ entity ) ; $ file = File :: load ( $ entity -> get ( 'fid' ) -> getValue ( ) [ 0 ] [ 'value' ] ) ; $ uri = $ file -> getFileUri ( ) ; $ response = new BinaryFileResponse ( $ uri ) ; $ response -> setContentDisposition ( ResponseHeaderBag :: DISPOSITION_ATTACHMENT ) ; return $ response ; }

Send file to browsers.

@param $file_hash
File link hash.

@return \Symfony\Component\HttpFoundation\BinaryFileResponse
Return file.


protected function getItemsFromData($columns)
    {
        $items = [];

        foreach ($this->data as $key => $item) {
            foreach ($columns as $column) {
                $fieldName = $column->getField();
                $fieldValue = '';

                if ($this instanceof Entity) {
                    // Mapped field
                    $itemEntity = $item;
                    if (strpos($fieldName, '.') === false) {
                        $functionName = ucfirst($fieldName);
                    } else {
                        // loop through all elements until we find the final entity and the name of the value for which we are looking
                        $elements = explode('.', $fieldName);
                        while ($element = array_shift($elements)) {
                            if (count($elements) > 0) {
                                $itemEntity = call_user_func([$itemEntity, 'get' . $element]);
                            } else {
                                $functionName = ucfirst($element);
                            }
                        }
                    }

                    // Get value of the column
                    if (isset($itemEntity->$fieldName)) {
                        $fieldValue = $itemEntity->$fieldName;
                    } elseif (is_callable([$itemEntity, $fullFunctionName = 'get' . $functionName])
                           || is_callable([$itemEntity, $fullFunctionName = 'has' . $functionName])
                           || is_callable([$itemEntity, $fullFunctionName = 'is' . $functionName])) {
                        $fieldValue = call_user_func([$itemEntity, $fullFunctionName]);
                    } else {
                        throw new PropertyAccessDeniedException(sprintf('Property "%s" is not public or has no accessor.', $fieldName));
                    }
                } elseif (isset($item[$fieldName])) {
                    $fieldValue = $item[$fieldName];
                }

                $items[$key][$fieldName] = $fieldValue;
            }
        }

        return $items;
    }

Gets an array of data items for rows from the set data.

@return array

Gets an array of data items for rows from the set data .


public function getTotalCountFromData($maxResults = null)
    {
        return $maxResults === null ? $this->count : min($this->count, $maxResults);
    }

public function getTotalCountFromData ( $ maxResults = null ) { return $ maxResults === null ? $ this -> count : min ( $ this -> count , $ maxResults ) ; }

Get Total count of data items.

@return int


protected function prepareStringForLikeCompare($input, $type = null)
    {
        if ($type === 'array') {
            $outputString = str_replace(':{i:0;', ':{', serialize($input));
        } else {
            $outputString = $this->removeAccents($input);
        }

        return $outputString;
    }

protected function prepareStringForLikeCompare ( $ input , $ type = null ) { if ( $ type === 'array' ) { $ outputString = str_replace ( ':{i:0;' , ':{' , serialize ( $ input ) ) ; } else { $ outputString = $ this -> removeAccents ( $ input ) ; } return $ outputString ; }

Prepares string to have almost the same behaviour as with a database,
removing accents and latin special chars.

@param mixed  $inputString
@param string $type        for array type, will serialize datas

@return string the input, serialized for arrays or without accents for strings

Prepares string to have almost the same behaviour as with a database removing accents and latin special chars .


public function setData($data)
    {
        $this->data = $data;

        if (!is_array($this->data) || empty($this->data)) {
            throw new \InvalidArgumentException('Data should be an array with content');
        }

        // This seems to exclude ...
        if (is_object(reset($this->data))) {
            foreach ($this->data as $key => $object) {
                $this->data[$key] = (array) $object;
            }
        }

        // ... this other (or vice versa)
        $firstRaw = reset($this->data);
        if (!is_array($firstRaw) || empty($firstRaw)) {
            throw new \InvalidArgumentException('Data should be a two-dimentional array');
        }
    }

Set a two-dimentional array.

@param array $data

@throws \InvalidArgumentException


private function resolveType($type)
    {
        if (!$type instanceof GridTypeInterface) {
            if (!is_string($type)) {
                throw new UnexpectedTypeException($type, 'string, APY\DataGridBundle\Grid\GridTypeInterface');
            }

            $type = $this->registry->getType($type);
        }

        return $type;
    }

Returns an instance of type.

@param string|GridTypeInterface $type The type of the grid

@return GridTypeInterface


private function resolveOptions(GridTypeInterface $type, Source $source = null, array $options = [])
    {
        $resolver = new OptionsResolver();

        $type->configureOptions($resolver);

        if (null !== $source && !isset($options['source'])) {
            $options['source'] = $source;
        }

        $options = $resolver->resolve($options);

        return $options;
    }

private function resolveOptions ( GridTypeInterface $ type , Source $ source = null , array $ options = [ ] ) { $ resolver = new OptionsResolver ( ) ; $ type -> configureOptions ( $ resolver ) ; if ( null !== $ source && ! isset ( $ options [ 'source' ] ) ) { $ options [ 'source' ] = $ source ; } $ options = $ resolver -> resolve ( $ options ) ; return $ options ; }

Returns the options resolved.

@param GridTypeInterface $type
@param Source            $source
@param array             $options

@return array


public function getGrid(Twig_Environment $environment, $grid, $theme = null, $id = '', array $params = [], $withjs = true)
    {
        $this->initGrid($grid, $theme, $id, $params);

        // For export
        $grid->setTemplate($theme);

        return $this->renderBlock($environment, 'grid', ['grid' => $grid, 'withjs' => $withjs]);
    }

public
function
getGrid
(
Twig_Environment
$
environment
,
$
grid
,
$
theme
=
,
$
id
=
''
,
array
$
params
=
[
]
,
$
withjs
=
true
)
{
$
this
->
initGrid
(
$
grid
,
$
theme
,
$
id
,
$
params
)
;
// For export
$
grid
->
setTemplate
(
$
theme
)
;
return
$
this
->
renderBlock
(
$
environment
,
'grid'
,
[
'grid'
=>
$
grid
,
'withjs'
=>
$
withjs
]
)
;
}

Render grid block.

@param Twig_Environment              $environment
@param \APY\DataGridBundle\Grid\Grid $grid
@param string                        $theme
@param string                        $id

@return string


public function getGridCell(Twig_Environment $environment, $column, $row, $grid)
    {
        $value = $column->renderCell($row->getField($column->getId()), $row, $this->router);

        $id = $this->names[$grid->getHash()];

        if (($id != '' && ($this->hasBlock($environment, $block = 'grid_' . $id . '_column_' . $column->getRenderBlockId() . '_cell')
                    || $this->hasBlock($environment, $block = 'grid_' . $id . '_column_' . $column->getType() . '_cell')
                    || $this->hasBlock($environment, $block = 'grid_' . $id . '_column_' . $column->getParentType() . '_cell')
                    || $this->hasBlock($environment, $block = 'grid_' . $id . '_column_id_' . $column->getRenderBlockId() . '_cell')
                    || $this->hasBlock($environment, $block = 'grid_' . $id . '_column_type_' . $column->getType() . '_cell')
                    || $this->hasBlock($environment, $block = 'grid_' . $id . '_column_type_' . $column->getParentType() . '_cell')))
            || $this->hasBlock($environment, $block = 'grid_column_' . $column->getRenderBlockId() . '_cell')
            || $this->hasBlock($environment, $block = 'grid_column_' . $column->getType() . '_cell')
            || $this->hasBlock($environment, $block = 'grid_column_' . $column->getParentType() . '_cell')
            || $this->hasBlock($environment, $block = 'grid_column_id_' . $column->getRenderBlockId() . '_cell')
            || $this->hasBlock($environment, $block = 'grid_column_type_' . $column->getType() . '_cell')
            || $this->hasBlock($environment, $block = 'grid_column_type_' . $column->getParentType() . '_cell')
        ) {
            return $this->renderBlock($environment, $block, ['grid' => $grid, 'column' => $column, 'row' => $row, 'value' => $value]);
        }

        return $this->renderBlock($environment, 'grid_column_cell', ['grid' => $grid, 'column' => $column, 'row' => $row, 'value' => $value]);
    }

public function getGridCell ( Twig_Environment $ environment , $ column , $ row , $ grid ) { $ value = $ column -> renderCell ( $ row -> getField ( $ column -> getId ( ) ) , $ row , $ this -> router ) ; $ id = $ this -> names [ $ grid -> getHash ( ) ] ; if ( ( $ id != '' && ( $ this -> hasBlock ( $ environment , $ block = 'grid_' . $ id . '_column_' . $ column -> getRenderBlockId ( ) . '_cell' ) || $ this -> hasBlock ( $ environment , $ block = 'grid_' . $ id . '_column_' . $ column -> getType ( ) . '_cell' ) || $ this -> hasBlock ( $ environment , $ block = 'grid_' . $ id . '_column_' . $ column -> getParentType ( ) . '_cell' ) || $ this -> hasBlock ( $ environment , $ block = 'grid_' . $ id . '_column_id_' . $ column -> getRenderBlockId ( ) . '_cell' ) || $ this -> hasBlock ( $ environment , $ block = 'grid_' . $ id . '_column_type_' . $ column -> getType ( ) . '_cell' ) || $ this -> hasBlock ( $ environment , $ block = 'grid_' . $ id . '_column_type_' . $ column -> getParentType ( ) . '_cell' ) ) ) || $ this -> hasBlock ( $ environment , $ block = 'grid_column_' . $ column -> getRenderBlockId ( ) . '_cell' ) || $ this -> hasBlock ( $ environment , $ block = 'grid_column_' . $ column -> getType ( ) . '_cell' ) || $ this -> hasBlock ( $ environment , $ block = 'grid_column_' . $ column -> getParentType ( ) . '_cell' ) || $ this -> hasBlock ( $ environment , $ block = 'grid_column_id_' . $ column -> getRenderBlockId ( ) . '_cell' ) || $ this -> hasBlock ( $ environment , $ block = 'grid_column_type_' . $ column -> getType ( ) . '_cell' ) || $ this -> hasBlock ( $ environment , $ block = 'grid_column_type_' . $ column -> getParentType ( ) . '_cell' ) ) { return $ this -> renderBlock ( $ environment , $ block , [ 'grid' => $ grid , 'column' => $ column , 'row' => $ row , 'value' => $ value ] ) ; } return $ this -> renderBlock ( $ environment , 'grid_column_cell' , [ 'grid' => $ grid , 'column' => $ column , 'row' => $ row , 'value' => $ value ] ) ; }

Cell Drawing override.

@param Twig_Environment                       $environment
@param \APY\DataGridBundle\Grid\Column\Column $column
@param \APY\DataGridBundle\Grid\Row           $row
@param \APY\DataGridBundle\Grid\Grid          $grid

@return string