I'm trying to integrate Evernote SDK to my CodeIgniter web application and some classes from the library are loaded and others not, :-S I can't see why.
I have that simple piece of code:
$access_token = 'my validated access token ';
// Get User Store
$userStoreTrans;
try{
$userStoreTrans = new THttpClient(USER_STORE_HOST, USER_STORE_PORT, USER_STORE_URL, USER_STORE_PROTO);
}
catch(TTransportException $e)
{
print $e->errorCode.' Message:'.$e->parameter;
}
$userStoreProt = new TBinaryProtocol($userStoreTrans);
$userStoreClient = new UserStoreClient($userStoreProt, $userStoreProt);
While $userStoreTrans and $userStoreProt are created correctly, a THttpClient and TBinaryProtocol classes from Evernote SDK, $userStoreClient throws a Class 'UserStoreClient' not found in .../home.php
I don't understand why some classes are recognized and some others not, the main difference that I can see is that "TClasses" are under evernote-sdk-php/lib/transport/*.php and evernote-sdk-php/lib/protocol/*.php and UserStoreClient has an extra folder evernote-sdk-php/lib/packages/UserStore/*.php
I'll explain how I'm including evernote-sdk-php to my CodeIgniter installation:
This is my CodeIgniter config/autoload.php
$autoload['libraries'] = array('database','session','form_validation','security','tank_auth','Evernote_bootstrap');
This is my Evernote_bootstrap.php file
<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
define("EVERNOTE_LIBS", dirname(__FILE__) . DIRECTORY_SEPARATOR . "evernote-sdk-php/lib");
// add ourselves to include path
ini_set("include_path", ini_get("include_path") . ":" . EVERNOTE_LIBS);
require_once("evernote-sdk-php/lib/autoload.php");
require_once("evernote-oauth/functions.php");
class Evernote_Bootstrap
{
function __construct()
{
// log_message('debug','Evernote_Bootstrap');
}
}
?>
The main purpose of Evernote_Bootstrap class is the require_once of evernote-sdk-php/lib/autoload.php, this class is auto-generated using the -phpa Thrift generator flag, I only add some logging to see the problem.
autoload.php:
<?php
/**
* Copyright (c) 2006- Facebook
* Distributed under the Thrift Software License
*
* See accompanying file LICENSE or visit the Thrift site at:
* http://developers.facebook.com/thrift/
*
* #package thrift
* #author Mark Slee <mcslee#facebook.com>
*/
/**
* Include this file if you wish to use autoload with your PHP generated Thrift
* code. The generated code will *not* include any defined Thrift classes by
* default, except for the service interfaces. The generated code will populate
* values into $GLOBALS['THRIFT_AUTOLOAD'] which can be used by the autoload
* method below. If you have your own autoload system already in place, rename your
* __autoload function to something else and then do:
* $GLOBALS['AUTOLOAD_HOOKS'][] = 'my_autoload_func';
*
* Generate this code using the -phpa Thrift generator flag.
*/
/**
* This parses a given filename for classnames and populates
* $GLOBALS['THRIFT_AUTOLOAD'] with key => value pairs
* where key is lower-case'd classname and value is full path to containing file.
*
* #param String $filename Full path to the filename to parse
*/
function scrapeClasses($filename) {
$fh = fopen($filename, "r");
while ($line = fgets($fh)) {
$matches = array();
if ( preg_match("/^\s*class\s+([^\s]+)/", $line, $matches)) {
if (count($matches) > 1)
$GLOBALS['THRIFT_AUTOLOAD'][strtolower($matches[1])] = $filename;
}
}
}
function findFiles($dir, $pattern, &$finds) {
if (! is_dir($dir))
return;
if (empty($pattern))
$pattern = "/^[^\.][^\.]?$/";
$files = scandir($dir);
if (!empty($files)) {
foreach ($files as $f) {
if ($f == "." || $f == "..")
continue;
if ( is_file($dir . DIRECTORY_SEPARATOR . $f) && preg_match($pattern, $f)) {
$finds[] = $dir . DIRECTORY_SEPARATOR . $f;
} else if ( is_dir($dir . DIRECTORY_SEPARATOR . $f) && substr($f, 0, 1) != ".") {
findFiles($dir . DIRECTORY_SEPARATOR . $f, $pattern, $finds);
}
}
}
}
function str_var_dump($object)
{
ob_start();
var_dump($object);
$dump = ob_get_clean();
return $dump;
}
// require Thrift core
require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . "Thrift.php");
if (! isset($GLOBALS['THRIFT_ROOT']))
$GLOBALS['THRIFT_ROOT'] = dirname(__FILE__);
log_message('debug','bootstrap autoload.php is executing');
// stuff for managing autoloading of classes
$GLOBALS['THRIFT_AUTOLOAD'] = array();
$GLOBALS['AUTOLOAD_HOOKS'] = array();
$THRIFT_AUTOLOAD =& $GLOBALS['THRIFT_AUTOLOAD'];
// only populate if not done so already
if (empty($GLOBALS['THRIFT_AUTOLOAD'])) {
//$allLibs = glob( dirname(__FILE__) . "/**/*.php"); // oh poor winblows users can't use glob recursively
$allLibs = array();
findFiles( dirname(__FILE__), "/\.php$/i", $allLibs);
log_message('debug',str_var_dump($allLibs));
if (!empty($allLibs)) {
foreach ($allLibs as $libFile) {
scrapeClasses($libFile);
}
log_message('debug','all scrapped classes: ' . str_var_dump($GLOBALS['THRIFT_AUTOLOAD']));
}
}else{
log_message('debug','$GLOBALS[THRIFT_AUTOLOAD] already defined');
}
// main autoloading
if (!function_exists('__autoload')) {
function __autoload($class) {
log_message('debug','__autoload');
global $THRIFT_AUTOLOAD;
$classl = strtolower($class);
if (isset($THRIFT_AUTOLOAD[$classl])) {
// log_message('debug','$THRIFT_AUTOLOAD[$classl] is set, do require_once');
//include_once $GLOBALS['THRIFT_ROOT'].'/packages/'.$THRIFT_AUTOLOAD[$classl];
require_once($THRIFT_AUTOLOAD[$classl]);
} else if (!empty($GLOBALS['AUTOLOAD_HOOKS'])) {
log_message('debug','$GLOBALS[AUTOLOAD_HOOKS]is no empty, lets foreach');
foreach ($GLOBALS['AUTOLOAD_HOOKS'] as $hook) {
// log_message('debug','iterate');
$hook($class);
}
} else {
log_message('debug','nothing to do');
}
}
}
Then I logged that library and seems to work fine. You can see the main important logs: log_message('debug',str_var_dump($allLibs)); and log_message('debug','all scrapped classes: ' . str_var_dump($GLOBALS['THRIFT_AUTOLOAD'])); and the output: http://pastebin.com/8w1MCKx9:
As you can see, UserStore class seems to be equally fine loaded than THttpClient or TBinaryProtocol... any idea about the problem?
I don't know if is important but I notice that $GLOBALS['THRIFT_ROOT'], defined on autoload.php, is not accesible from a CI Controller. Probably I'm missing something about CI architecture.
As of our lastest SDK updates, UserStoreClient (and the other SDK classes) are in appropriate namespaces. Assuming that you're using our generated code, have you imported the classes you're using? E.g.
use EDAM\UserStore\UserStoreClient;
Related
I'm working on a project whereby I have the following file structure:
index.php
|---lib
|--|lib|type|class_name.php
|--|lib|size|example_class.php
I'd like to auto load the classes, class_name and example_class (named the same as the PHP classes), so that in index.php the classes would already be instantiated so I could do:
$class_name->getPrivateParam('name');
I've had a look on the net but can't quite find the right answer - can anyone help me out?
EDIT
Thanks for the replies. Let me expand on my scenario. I'm trying to write a WordPress plugin that can be dropped into a project and additional functionality added by dropping a class into a folder 'functionality' for example, inside the plugin. There will never be 1000 classes, at a push maybe 10?
I could write a method to iterate through the folder structure of the 'lib' folder, including every class then assigning it to a variable (of the class name), but didn't think that was a very efficient way to do it but it perhaps seems that's the best way to achieve what I need?
Please, if you need to autoload classes - use the namespaces and class names conventions with SPL autoload, it will save your time for refactoring.
And of course, you will need to instantiate every class as an object.
Thank you.
Like in this thread:
PHP Autoloading in Namespaces
But if you want a complex workaround, please take a look at Symfony's autoload class:
https://github.com/symfony/ClassLoader/blob/master/ClassLoader.php
Or like this (I did it in one of my projects):
<?
spl_autoload_register(function($className)
{
$namespace=str_replace("\\","/",__NAMESPACE__);
$className=str_replace("\\","/",$className);
$class=CORE_PATH."/classes/".(empty($namespace)?"":$namespace."/")."{$className}.class.php";
include_once($class);
});
?>
and then you can instantiate your class like this:
<?
$example=new NS1\NS2\ExampleClass($exampleConstructParam);
?>
and this is your class (found in /NS1/NS2/ExampleClass.class.php):
<?
namespace NS1\NS2
{
class Symbols extends \DB\Table
{
public function __construct($param)
{
echo "hello!";
}
}
}
?>
If you have an access to the command line, you can try it with composer in the classMap section with something like this:
{
"autoload": {
"classmap": ["yourpath/", "anotherpath/"]
}
}
then you have a wordpress plugin to enable composer in the wordpress cli : http://wordpress.org/plugins/composer/
function __autoload($class_name) {
$class_name = strtolower($class_name);
$path = "{$class_name}.php";
if (file_exists($path)) {
require_once($path);
} else {
die("The file {$class_name}.php could not be found!");
}
}
UPDATE:
__autoload() is deprecated as of PHP 7.2
http://php.net/manual/de/function.spl-autoload-register.php
spl_autoload_register(function ($class) {
#require_once('lib/type/' . $class . '.php');
#require_once('lib/size/' . $class . '.php');
});
I have an example here that I use for autoloading and initiliazing.
Basically a better version of spl_autoload_register since it only tries to require the class file whenever you initializes the class.
Here it automatically gets every file inside your class folder, requires the files and initializes it. All you have to do, is name the class the same as the file.
index.php
<?php
require_once __DIR__ . '/app/autoload.php';
$loader = new Loader(false);
User::dump(['hello' => 'test']);
autoload.php
<?php
class Loader
{
public static $library;
protected static $classPath = __DIR__ . "/classes/";
protected static $interfacePath = __DIR__ . "/classes/interfaces/";
public function __construct($requireInterface = true)
{
if(!isset(static::$library)) {
// Get all files inside the class folder
foreach(array_map('basename', glob(static::$classPath . "*.php", GLOB_BRACE)) as $classExt) {
// Make sure the class is not already declared
if(!in_array($classExt, get_declared_classes())) {
// Get rid of php extension easily without pathinfo
$classNoExt = substr($classExt, 0, -4);
$file = static::$path . $classExt;
if($requireInterface) {
// Get interface file
$interface = static::$interfacePath . $classExt;
// Check if interface file exists
if(!file_exists($interface)) {
// Throw exception
die("Unable to load interface file: " . $interface);
}
// Require interface
require_once $interface;
//Check if interface is set
if(!interface_exists("Interface" . $classNoExt)) {
// Throw exception
die("Unable to find interface: " . $interface);
}
}
// Require class
require_once $file;
// Check if class file exists
if(class_exists($classNoExt)) {
// Set class // class.container.php
static::$library[$classNoExt] = new $classNoExt();
} else {
// Throw error
die("Unable to load class: " . $classNoExt);
}
}
}
}
}
/*public function get($class)
{
return (in_array($class, get_declared_classes()) ? static::$library[$class] : die("Class <b>{$class}</b> doesn't exist."));
}*/
}
You can easily manage with a bit of coding, to require classes in different folders too. Hopefully this can be of some use to you.
You can specify a namespaces-friendly autoloading using this autoloader.
<?php
spl_autoload_register(function($className) {
$file = __DIR__ . '\\' . $className . '.php';
$file = str_replace('\\', DIRECTORY_SEPARATOR, $file);
if (file_exists($file)) {
include $file;
}
});
Make sure that you specify the class file's location corretly.
Source
spl_autoload_register(function ($class_name) {
$iterator = new DirectoryIterator(dirname(__FILE__));
$files = $iterator->getPath()."/classes/".$class_name.".class.php";
if (file_exists($files)) {
include($files);
} else {
die("Warning:The file {$files}.class.php could not be found!");
}
});
do this in a file and called it anything like (mr_load.php)
this were u put all your classes
spl_autoload_register(function($class){
$path = '\Applicaton/classes/';
$extension = '.php';
$fileName = $path.$class.$extension;
include $_SERVER['DOCUMENT_ROOT'].$fileName;
})
;
then create another file and include mr_load.php; $load_class = new BusStop(); $load_class->method()
Codeigniter does not find trivial classes:
Unable to load the requested class: Bcrypt
But the same goes for custom made classes defined in files in application/libraries/. I am used that django lists the folders where it searched for a file, but did not find one. Obviously CI must also iterate over some list of folders or files, but is not as polite to display them along with the error.
It seems as if CI has a naming convention to deduce the (set of) filename(s) where it would expect a class to be. How can I programmatically error_log the list of folders or filenames that Codeigniter or PHP tried to track down this class?
EDIT: The lines of code that produce such a loading-error are:
$autoload['libraries'] = array('database','session','mi_file_fetcher');
in application/config/autoload.php and
$this->load->library("bcrypt");
in application/models/User.php
As stated in the comments, I was not asking for a fix, I was asking for a list.
I managed to do so by updating system/core/Loader.php
protected function _ci_load_library_files_tried($class, $subdir, $params, $object_name)
{
$files_tried = array(BASEPATH . 'libraries/' . $subdir . $class . '.php');
foreach ($this->_ci_library_paths as $path) {
if ($path === BASEPATH) {
continue;
}
array_push($files_tried, $path . 'libraries/' . $subdir . $class . '.php');
}
return $files_tried;
}
protected function _ci_load_library($class, $params = NULL, $object_name = NULL)
{
// ...
// If we got this far we were unable to find the requested class.
$files_tried = $this->_ci_load_library_files_tried($class, $subdir, $params, $object_name);
log_message('error', 'Unable to load the requested class: '.$class .
", tried these files:\n" . join("\n", $files_tried));
show_error('Unable to load the requested class: '.$class .
', tried these files:<ul><li>' . join('</li><li>', $files_tried) . '</li></ul>');
}
Would be great if CI actually provided decent debugging information.
The CodeIgniter (CI) documentation does tell you the default locations of libraries, models, helpers, views and many other framework objects. There isn't a section that explicitly lists the folders though. The Loader Class documentation does tell you, but you have to dig for it a bit.
The subtopics on the General Topics section of the docs clearly state the default locations for the various classes the frameworks uses and where to put custom classes.
In most cases following the prescribed file structure and using the loader class, e.g. (Bcrypt.php is in /application/libraries/)
$this->load->library('bcrypt');
works perfectly.
There are cases (none of which seem to be involved in your problem) where CI needs help. Rather than hack or extend CI_Loader an autoloader is useful in these cases.
There are lots of ways to add an autoloader but my preference is to use CI's Hooks feature. Here's how.
In config.php set 'enable_hooks' to TRUE
$config['enable_hooks'] = TRUE;
These lines go in /application/config/hooks.php
$hook['pre_system'][] = array(
'class' => '',
'function' => 'register_autoloader',
'filename' => 'Auto_load.php',
'filepath' => 'hooks'
);
The following is the contents of /application/hooks/Auto_load.php
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
function register_autoloader()
{
spl_autoload_register('my_autoloader');
}
/**
* Allows classes that do not start with CI_ and that are
* stored in these subdirectories of `APPPATH`
* (default APPPATH = "application/" and is defined in "index.php")
* libraries,
* models,
* core
* controllers
* to be instantiated when needed.
* #param string $class Class name to check for
* #return void
*/
function my_autoloader($class)
{
if(strpos($class, 'CI_') !== 0)
{
if(file_exists($file = APPPATH.'libraries/'.$class.'.php'))
{
require_once $file;
}
elseif(file_exists($file = APPPATH.'models/'.$class.'.php'))
{
require_once $file;
}
elseif(file_exists($file = APPPATH.'core/'.$class.'.php'))
{
require_once $file;
}
elseif(file_exists($file = APPPATH.'controllers/'.$class.'.php'))
{
require_once $file;
}
}
}
The function log_message($level, $message) could be used in the above if you wanted.
If you are using some other creative folder structure you will have to modify the above to accommodate that layout.
I use CakePHP 2.9.8 to develop an old application that was written 7 years ago and developed until now.
unfortunately the first developer add some codes in CakePHP's library and for migrating to the 3th version of CakePHP I need to transfer the changing in the application.
one of the changing is in the App::load located in ~\lib\Cake\Core\App.php and as it used static::_map($file, $className, $plugin);, I can write a class which extend App.php and rewrite the _map function.
My questions:
Can be override a protected function or property?
if no:
why in CakePHP they were used (or called) like static:: for example: static::_map($file, $className, $plugin); but the definition is protected static function _map($file, $name, $plugin = null)
if yes:
Where in my application should I define class Foo which extend App and for load function that I want to remove the developer changes, where should I write Foo::load?.
I put also App::load function here:
public static function load($className) {
if (!isset(static::$_classMap[$className])) {
return false;
}
if (strpos($className, '..') !== false) {
return false;
}
$parts = explode('.', static::$_classMap[$className], 2);
list($plugin, $package) = count($parts) > 1 ? $parts : array(null, current($parts));
$file = static::_mapped($className, $plugin);
if ($file) {
return include $file;
}
$paths = static::path($package, $plugin);
if (empty($plugin)) {
$appLibs = empty(static::$_packages['Lib']) ? APPLIBS : current(static::$_packages['Lib']);
$paths[] = $appLibs . $package . DS;
$paths[] = APP . $package . DS;
$paths[] = CAKE . $package . DS;
} else {
$pluginPath = CakePlugin::path($plugin);
$paths[] = $pluginPath . 'Lib' . DS . $package . DS;
$paths[] = $pluginPath . $package . DS;
}
$normalizedClassName = str_replace('\\', DS, $className);
// Start point of custom codes
// Load Custom Classes that are usually added during customizations
// This part is for accepting a class like **XControllerCustom** but the name of file is: **XController**
if($package === 'Model'){
foreach ($paths as $path) {
$file = $path . DS . $className . '.php';
$file_custom = $path . 'Custom' . DS . $normalizedClassName . '.php';
if (file_exists($file_custom) && file_exists($file)) {
self::_map($file_custom, $className);
include $file;
return include $file_custom;
}
}
}
// End of custom's code
foreach ($paths as $path) {
$file = $path . $normalizedClassName . '.php';
if (file_exists($file)) {
static::_map($file, $className, $plugin);
return include $file;
}
}
return false;
}
You have some serious lack of knowledge in OOP in php. Read these links carefully, they'll give you the answers and hopefully a complete understandig of both topics.
http://php.net/manual/en/language.oop5.visibility.php
http://php.net/manual/en/language.oop5.late-static-bindings.php
Also there are plenty of questions and answers related to both on SO. Just search for them if the manual is not sufficient.
For number three, there is no fixed place. Just put it in a Lib or Utility folder inside your app. However, I would choose an autoloader over the built in App stuff and just use uses to make my classes available. If your app doesn't use composer yet, just add it and use its autoader and namespaces. The only reason 2.x is still not using them is backward compatibility. App is a crutch to get namespace like functionality done without actually using them.
This is an app that's intended to generate entities, allowing the end user to define the fields, and it worked like charm, until Sf 2.3 and PHP version something(don't recall the version that was running) on a debian 7. But now it's on a FreeBSD with PHP 5.4.28 and SF 2.4
It can create an entity from scratch or recreate and overwrite it with changes.. but all of a suden, the autoloader stopped (re-)loading the class after it's created. It wont be until next postback, that it actually sees the class, obviously if I don't create it again (e.g. commenting code).
So, The question is How can I force autoload of a class whose code had been written and saved to the class' php file during the request handling..?
The following code gives class not found.
/**
* Handles creation of the Doctrine Entity that will represent de Document being
* created.
*
* #param DocumentType $docDefinition The Document definition
*
* #return string Genedated Document's class shortcut notation.
*
* #throws \Exception if {#link $this->doctrineGenEntityCmd}->run() fails or throws
*/
protected function createDocumentEntity(DocumentType $docDefinition)
{
$this->log->info("Creating Document entity");
try {
list($exitCode, $out) =
$this->runCommand('generate:doctrine:entity', $this->generateEntityCmd, $docDefinition);
} catch (\Exception $exc) {
throw new \Exception(
sprintf("Can't create Document due to following: \n %s", $exc->getTraceAsString()),
null,
$exc
);
}
if (0 != $exitCode) {
throw new \Exception(
sprintf("Can't create Document due to following: \n %s", $out->getErrorOutput())
);
}
$justToAutoload = $this->tryInstantiate($docDefinition);
$this->log->info($justToAutoload);
return InputHandler\InputHandler::getClassShortcutNotation($docDefinition);
}
Inside the debugging fn tryInstantiate() I tried several things, unregistering autoloaders, re-including composer's (app/autoload.php)
protected function tryInstantiate(DocumentType $docDefinition)
{
$fqcn = "\\" . $this->getDocumentFqcn($docDefinition);
$log = $this->log;
$functions = spl_autoload_functions();
foreach($functions as $function) {
spl_autoload_unregister($function);
}
$newLoader = include $this->kernel->getRootDir() . '/autoload.php';
$newLoader->register(true);
$log->info('tryInstantiate');
// spl_autoload_register(function ($className) use ($fqcn, $log) {
// if ($className == $fqcn) {
// $log->info("Loading: " . $className);
// $baseDir = $this->kernel->getRootDir() . '/../src';
// $classFile = $baseDir . str_replace('\\', DIRECTORY_SEPARATOR , $fqcn) . '.php';
// include $classFile;
// }
// }, true, true);
//$fqcn = str_replace('\\', '\', $fqcn);
return $test = new \DocDigital\Bundle\DocumentBundle\Entity\CustomDocument\Test();
die(var_dump($test));
return new $fqcn();
try {
return $deletMe = new \DocDigital\Bundle\DocumentBundle\Entity\CustomDocument\Test1();
return new $fqcn();
} catch (\Exception $exc) {
sprintf("Can't instantiate %s due to the following: \n %s", $fqcn, $exc->getTraceAsString());
}
}
always with the same result:
php.CRITICAL: Fatal Error: Class 'bundle\Entity\namespace\Test' not found
Any help is appreciated.
Thanks!
I need to create CRX file on the fly. It's for my CMS backend, so it will be just for authenticated users who can install CMS backend as webapp and offer some more privileges to the web app. The problem is, that the backend is used for many domains so creating CRX file for each of them is quite a work. So I figured that it would be easier to just create CRX file on demand which would be generated by PHP using its own domain and probably custom icon.
On the documentation page, they explain the CRX package format. There are many third party libraries that implemented that format. In the following page, you can learn the format and either download a Ruby / Bash script (you can find others too online), and if you want to implement your own packager, you can follow the format described there.
https://developer.chrome.com/extensions/crx
If you really don't want to follow the format, you can let your PHP script do one of the following:
Use Chrome binary chrome.exe --pack-extension=c:\myext --pack-extension-key=c:\myext.pem
Use the Ruby or Bash script from PHP (you can call system commands)
Hope that helps!
Also, for anyone still looking for a way to create a CTX in PHP, look at this question: Create Google Chrome Crx file with PHP
This works for me :D I just change from real path to null without that changes won't work on new chrome :D
/**
* Class CrxGenerator
*
* Create Chrome Extension CRX packages from
* folder & pem private key
*
* Based on CRX format documentation: http://developer.chrome.com/extensions/crx.html
*
* #author: Tomasz Banasiak
* #license: MIT
* #date: 2013-11-03
*/
class CrxGenerator {
const TEMP_ARCHIVE_EXT = '.zip';
private $sourceDir = null;
private $cacheDir = '';
private $privateKeyContents = null;
private $publicKeyContents = null;
private $privateKey = null;
private $publicKey = null;
/**
* #param $file Path to PEM key
* #throws Exception
*/
public function setPrivateKey($file) {
if (!file_exists($file)) {
throw new Exception('Private key file does not exist');
}
$this->privateKeyContents = file_get_contents($file);
$this->privateKey = $file;
}
/**
* #param $file Path to PUB key
* #throws Exception
*/
public function setPublicKey($file) {
if (!file_exists($file)) {
throw new Exception('Private key file does not exist');
}
$this->publicKeyContents = file_get_contents($file);
$this->publicKey = $file;
}
/**
* #param $cacheDir dir specified for caching temporary archives
* #throws Exception
*/
public function setCacheDir($cacheDir) {
if (!is_dir($cacheDir)) {
throw new Exception('Cache dir does not exist!');
}
$this->cacheDir = $cacheDir;
}
/**
* #param $sourceDir Extension source directory
*/
public function setSourceDir($sourceDir) {
$this->sourceDir = $sourceDir;
}
/**
* #param $outputFile path to output file
* #throws Exception
*/
public function generateCrx($outputFile) {
$basename = basename($outputFile);
// First step - create ZIP archive
$zipArchive = $this->cacheDir . DIRECTORY_SEPARATOR . $basename . self::TEMP_ARCHIVE_EXT;
$result = $this->createZipArchive(
$this->sourceDir,
$zipArchive
);
if (!$result) {
throw new Exception('ZIP creation failed');
}
$zipContents = file_get_contents($zipArchive);
// Second step - create file signature
$privateKey = openssl_pkey_get_private($this->privateKeyContents);
openssl_sign($zipContents, $signature, $privateKey, 'sha1');
openssl_free_key($privateKey);
// Create output file
$crx = fopen($outputFile, 'wb');
fwrite($crx, 'Cr24');
fwrite($crx, pack('V', 2));
fwrite($crx, pack('V', strlen($this->publicKeyContents)));
fwrite($crx, pack('V', strlen($signature)));
fwrite($crx, $this->publicKeyContents);
fwrite($crx, $signature);
fwrite($crx, $zipContents);
fclose($crx);
// Clear cache
unset($zipArchive);
}
/**
* #param $source - source dir
* #param $outputFile - output file
* #return bool - success?
*/
private function createZipArchive($source, $outputFile) {
if (!extension_loaded('zip') || !file_exists($source)) {
return false;
}
$zip = new ZipArchive();
if (!$zip->open($outputFile, ZIPARCHIVE::CREATE)) {
return false;
}
$source = str_replace('\\', '/', realpath($source));
if (is_dir($source) === true) {
$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($source), RecursiveIteratorIterator::SELF_FIRST);
foreach ($files as $file) {
$file = str_replace('\\', '/', $file);
// Exclude "." and ".." folders
if( in_array(substr($file, strrpos($file, '/') + 1), array('.', '..')) ) {
continue;
}
$file = $file;
if (is_dir($file) === true) {
$zip->addEmptyDir(str_replace($source . '/', '', $file . '/'));
}
else if (is_file($file) === true) {
$zip->addFromString(str_replace($source . '/', '', $file), file_get_contents($file));
}
}
}
else if (is_file($source) === true) {
$zip->file_get_contents($source);
echo $source;
}
return $zip->close();
}
}
Looks like I have found exactly what I was looking for. Chrome team has made this option to create CRX-less web apps, just by using simple manifest file.
Much easier to create own webapp and publish it on website for installation. And it also solves my problem when I have many websites with a lot of domains and I don't have to create custom CRX file for each domain. I just create small PHP script which creates manifest files on the fly for each domain.