Pass a variable to language file without sprintf - php

I want to pass a variable to a language file. I have created MY_language.php in application/core/MY_language.php.
class MY_Language extends CI_Lang
{
public function __construct()
{
parent::__construct();
}
function line($line, $params = null)
{
$return = parent::line($line);
if ($return === false) {
return "!-- $line --!";
} else {
if (!is_null($params)) {
$return = $this->_ni_line($return, $params);
}
return $return;
}
}
private function _ni_line($str, $params)
{
$return = $str;
$params = is_array($params) ? $params : array($params);
$search = array();
$cnt = 0;
foreach ($params as $param) {
$search[$cnt] = '/\\$' . ($cnt + 1) . '/';
$cnt++;
}
$return = preg_replace($search, $params, $return);
return $return;
}
}
This file must override the CodeIgniter line() function and accept an array of parameters as input, and insert into string language everywhereIi have type $ in my language text.
$lang['delete'] = "$name was deleted";
The result of the above code is:
sam was deleted

in codeigniter 3 the language your core language file must be PREFIX_lang

Since you are adding parameters to the line() function you are unable to override it.
Use a different name like magic_line()

Related

Access multidimensional array with a path-like string

I have a function called get_config() and an array called $config.
I call the function by using get_config('site.name') and am looking for a way to return the value of $config so for this example, the function should return $config['site']['name'].
I'm nearly pulling my hair out - trying not to use eval()! Any ideas?
EDIT: So far I have:
function get_config($item)
{
global $config;
$item_config = '';
$item_path = '';
foreach(explode('.', $item) as $item_part)
{
$item_path .= $item."][";
$item = $config.{rtrim($item_path, "][")};
}
return $item;
}
This should work:
function get_config($config, $string) {
$keys = explode('.', $string);
$current = $config;
foreach($keys as $key) {
if(!is_array($current) || !array_key_exists($key, $current)) {
throw new Exception('index ' . $string . ' was not found');
}
$current = $current[$key];
}
return $current;
}
you could try something like...
function get_config($item)
{
global $config;
return $config[{str_replace('.','][',$item)}];
}
Inside your get_config function, you can parse the string using explode function in php
function get_config($data){
$pieces = explode(".", $data);
return $config[$pieces[0]][$pieces[1]];
}

Return in recursive function php

I've got a little problem with recursive function output. Here's the code:
function getTemplate($id) {
global $templates;
$arr = $templates[$id-1];
if($arr['parentId'] != 0) {
$arr['text'] .= str_replace($arr['attr'], $arr['text'], getTemplate($arr['parentId']));
}
return $arr['text'];
}
The problem is that that function returns a value on each iteration like this:
file.exe
category / file.exe
root / category / file.exe
And I need only the last string which resembles full path. Any suggestions?
//UPD: done, the problem was with the dot in $arr['text'] .= str_replace
Try this please. I know its using global variable but I think this should work
$arrGlobal = array();
function getTemplate($id) {
global $templates;
global $arrGlobal;
$arr = $templates[$id-1];
if($arr['parentId'] != 0) {
array_push($arrGlobal, getTemplate($arr['parentId']));
}
return $arr['text'];
}
$arrGlobal = array_reverse($arrGlobal);
echo implode('/',$arrGlobal);
Try this,
function getTemplate($id) {
global $templates;
$arr = $templates[$id-1];
if($arr['parentId'] != 0) {
return $arr['text'] .= str_replace($arr['attr'], $arr['text'], getTemplate($arr['parentId']));
}
}
Give this a try:
function getTemplate($id, array $templates = array())
{
$index = $id - 1;
if (isset($templates[$index])) {
$template = $templates[$index];
if (isset($template['parentId']) && $template['parentId'] != 0) {
$parent = getTemplate($template['parentId'], $templates);
$template['text'] .= str_replace($template['attr'], $template['text'], $parent);
}
return $template['text'];
}
return '';
}
$test = getTemplate(123, $templates);
echo $test;

CakePhp: Url based internationalization

I've a small problem with my internationalization:
I want to have some url looking like this: http://mywebsite/eng/controller/action/params...
I found this http://nuts-and-bolts-of-cakephp.com/2008/11/28/cakephp-url-based-language-switching-for-i18n-and-l10n-internationalization-and-localization/
This is working nice most of time. But I've one case where this hasn't the expected result.
When I'm using $this->Html->link with named parameters, I don't get my nice structure, but something like http://mywebsite/controller/action/paramX:aaa/paramxY:bbb/language:eng
I think this is a routing problem, but I can't figure what is going wrong?
Thank you very much
This is because cakephp doens't find a route in routes.php that corresponds to this link. In other words, you'll have to define this route in the routes.php file
Router::connect('/:language/:controller/:action/:paramX/:paramY');
Once this set, $this->Html->link will output a nice url
I finally did this:
I created a custom CakeRoute, in this cakeRoute, I override the "match" url and the _writeUrl method.
Now every thing is working like a charm :)
For those which are interessted by the route class:
<?php
class I18nRoute extends CakeRoute {
/**
* Constructor for a Route
* Add a regex condition on the lang param to be sure it matches the available langs
*
* #param string $template Template string with parameter placeholders
* #param array $defaults Array of defaults for the route.
* #param string $params Array of parameters and additional options for the Route
* #return void
* #access public
*/
public function __construct($template, $defaults = array(), $options = array()) {
//$defaults['language'] = Configure::read('Config.language');
$options = array_merge((array)$options, array(
'language' => join('|', Configure::read('Config.languages'))
));
parent::__construct($template, $defaults, $options);
}
/**
* Attempt to match a url array. If the url matches the route parameters + settings, then
* return a generated string url. If the url doesn't match the route parameters false will be returned.
* This method handles the reverse routing or conversion of url arrays into string urls.
*
* #param array $url An array of parameters to check matching with.
* #return mixed Either a string url for the parameters if they match or false.
* #access public
*/
public function match($url) {
if (empty($url['language'])) {
$url['language'] = Configure::read('Config.language');
}
if (!$this->compiled()) {
$this->compile();
}
$defaults = $this->defaults;
if (isset($defaults['prefix'])) {
$url['prefix'] = $defaults['prefix'];
}
//check that all the key names are in the url
$keyNames = array_flip($this->keys);
if (array_intersect_key($keyNames, $url) != $keyNames) {
return false;
}
$diffUnfiltered = Set::diff($url, $defaults);
$diff = array();
foreach ($diffUnfiltered as $key => $var) {
if ($var === 0 || $var === '0' || !empty($var)) {
$diff[$key] = $var;
}
}
//if a not a greedy route, no extra params are allowed.
if (!$this->_greedy && array_diff_key($diff, $keyNames) != array()) {
return false;
}
//remove defaults that are also keys. They can cause match failures
foreach ($this->keys as $key) {
unset($defaults[$key]);
}
$filteredDefaults = array_filter($defaults);
//if the difference between the url diff and defaults contains keys from defaults its not a match
if (array_intersect_key($filteredDefaults, $diffUnfiltered) !== array()) {
return false;
}
$passedArgsAndParams = array_diff_key($diff, $filteredDefaults, $keyNames);
list($named, $params) = Router::getNamedElements($passedArgsAndParams, $url['controller'], $url['action']);
//remove any pass params, they have numeric indexes, skip any params that are in the defaults
$pass = array();
$i = 0;
while (isset($url[$i])) {
if (!isset($diff[$i])) {
$i++;
continue;
}
$pass[] = $url[$i];
unset($url[$i], $params[$i]);
$i++;
}
/*
//still some left over parameters that weren't named or passed args, bail.
//We don't want this behavior, we use most of args for the matching, and if we have more, we just allow them as parameters
if (!empty($params)) {
return false;
}*/
//check patterns for routed params
if (!empty($this->options)) {
foreach ($this->options as $key => $pattern) {
if (array_key_exists($key, $url) && !preg_match('#^' . $pattern . '$#', $url[$key])) {
return false;
}
}
}
return $this->_writeUrl(array_merge($url, compact('pass', 'named')));
}
function _writeUrl($params) {
if (isset($params['prefix'], $params['action'])) {
$params['action'] = str_replace($params['prefix'] . '_', '', $params['action']);
unset($params['prefix']);
}
if (is_array($params['pass'])) {
$params['pass'] = implode('/', $params['pass']);
}
$instance =& Router::getInstance();
$separator = $instance->named['separator'];
if (!empty($params['named']) && is_array($params['named'])) {
$named = array();
foreach ($params['named'] as $key => $value) {
$named[] = $key . $separator . $value;
}
$params['pass'] = $params['pass'] . '/' . implode('/', $named);
}
$out = $this->template;
$search = $replace = array();
foreach ($this->keys as $key) {
$string = null;
if (isset($params[$key])) {
$string = $params[$key];
} elseif (strpos($out, $key) != strlen($out) - strlen($key)) {
$key .= '/';
}
$search[] = ':' . $key;
$replace[] = $string;
}
$out = str_replace($search, $replace, $out);
if (strpos($this->template, '*')) {
$out = str_replace('*', $params['pass'], $out);
}
$out = str_replace('//', '/', $out);
//Modified part: allows us to print unused parameters
foreach($params as $key => $value){
$found = false;
foreach($replace as $repValue){
if($value==$repValue){
$found=true;
break;
}
}
if(!$found && !empty($value)){
$out.="/$key:$value";
}
}
return $out;
}
}
And you can set the route like this:
Router::connect('/:language/:controller/*', array(), array('routeClass' => 'I18nRoute'));

How can I use __call to dynamically return property values

I've been trying to think of a way to dynamically return property values for a class using __call instead of creating a slew of functions whose only purpose would be to return those values. The idea I have is to be able to request ( for example ) $this->fetch_PersonFirstName() and have the class check if $this->person["first_name"] is set and return the value. Another example would be calling $this->fetch_BookGenreTitle() and have the class return the value of $this->book["genre"]["title"]. I know something like this would have to do a bit of checking in order for it to automatically determine that, for example, since $this->book["genre_title"] doesn't exist, then it should check for $this->book["genre"]["title"].
So far I've come up with code that ( for some reason ) works for returning the values of an array ( such as my person example ) but my problem quickly develops when I try to return the values of a multidimensional array ( such as with my above book example ). Bare in mind, I'm still trying to think of a way for the __call method to check for the existence of one, and if it doesn't exist, then the other.
Please, throw me a line here. I've been banging my head against the wall trying to figure this out and it's killing me.
<?php
class Test
{
protected $person;
protected $book;
public function __construct()
{
$this->person["first_name"] = "John Smith";
$this->book["genre"]["title"] = "Suspense";
}
public function __call($method, $args)
{
$args = implode(", ", $args);
if (preg_match("/^fetch_([A-Z]{1,}[a-z]{1,})(.*)?/", $method, $match))
{
print_r($match);
echo "<br><br>";
$property = strtolower($match[1]);
$indexes = $match[2];
if (property_exists("Test", $property))
{
if ($indexes)
{
$indexes = preg_split("/(?<=[a-z])(?=[A-Z])/", $indexes);
$num_indexes = count($indexes);
$count_indexes = 1;
for ($count=0; $count<$num_indexes; $count++)
{
$record = strtolower($indexes[$count]);
$index .= $record;
$array .= "{$record}";
$var_index = $index;
$var_array = $array;
echo $var_index." {$count}<br>";
echo $var_array." {$count}<br>";
//print_r($this->{$property}{$var_array});
if ($count_indexes == $num_indexes)
{
if (isset($this->{$property}{$var_index}))
{
return $this->{$property}{$var_index};
}
else
{
return $this->{$property}{$var_array};
}
}
else
{
$index .= "_";
}
$count_indexes++;
}
echo "<br><br>";
}
else
{
return $this->{$property};
}
}
}
}
}
?>
<?php
$test = new Test();
echo $test->fetch_PersonFirstName();
echo "<br><br>";
echo $test->fetch_BookGenreTitle();
?>
Thanks again folks. I think I finally have my solution, which works for multidimensional arrays or any depth and accounts for determining whether a property is, for example $this->book["genre_title"] or $this->book["genre"]["title"] :)
I'm posting the code below as someone else may randomly find it useful in the future
<?php
class Test
{
protected $person;
protected $book;
public function __construct()
{
$this->person["first_name"] = "John Smith";
$this->book["genre"]["title"] = "Suspense";
}
public function __get($var)
{
if (preg_match_all("/([A-Z][A-Z0-9]*(?=$|[A-Z][a-z0-9])|[A-Za-z][a-z0-9]+)/", $var, $matches))
{
$matches = $matches[1];
$property = strtolower($matches[0]);
if (property_exists($this, $property))
{
unset($matches[0]);
$matches = array_values($matches);
$num_matches = count($matches);
$var = &$this->{$property};
if (!$num_matches)
{
return $var;
}
else
{
foreach($matches as &$match)
{
$match = strtolower($match);
$index .= $match;
if ($probe = $this->iterateArray($var, $index))
{
$var = $probe;
unset($index);
}
elseif ($probe = $this->iterateArray($var, $index))
{
$var = $probe;
}
else
{
$index .= "_";
}
}
return $var;
}
}
}
}
public function iterateArray($var, $index)
{
if (array_key_exists($index, $var))
{
return $var[$index];
}
else
{
return false;
}
}
}
?>
<?php
$test = new Test();
echo $test->PersonFirstName;
echo "<br><br>";
echo $test->BookGenreTitle;
?>
There's more than likely some ways to improve/streamline the code, in which case anyone wanting to do so is more than welcome to post an improved version.
Given "BookGenreTitle" :
Use some sort of regex to separate "Book", "Genre", and "Title"
property_exists($this, "Book")
array_key_exists("genre", $this->book)
If key exists, return $this->book["genre"]
If key doesn't exist, array_key_exists("genre_title", $this->book)
If key exists, return $this->book["genre_title"]
If key doesn't exist, array_key_exists("genre", $this->book) && array_key_exists("title", $this->book["genre"])
Keep going
There's probably a way to use a loop or some sort of recursion instead of hard-coding the maximum depth, but I won't get into that now...
Oh, and as the other poster said, what you want is property overloading (__get and __set).
You need to take a look at property overloading, not method overloading as you've already figured out in the question's title yourself.

Get all modules, controllers and actions from a Zend Framework application

I want to create a Zend Controller for ACL management so my problem is: How can I get all Module names, Control names and Action names in a Zend application to build a ACL Control?
I use Zend_Navigation and if the resource don't exist in your ACL Zend_Navigation is thrown a exception. And I want to use a database to deny and allow access. So I must build the database first. And if I must do that by hand it's a pain to do that.
This may be an old question but this is how I am doing this...
// $front = Zend_Controller_Front::getInstance(); // use this line instead on a model class
$front = $this->getFrontController(); // this in controller
$acl = array();
foreach ($front->getControllerDirectory() as $module => $path) {
foreach (scandir($path) as $file) {
if (strstr($file, "Controller.php") !== false) {
include_once $path . DIRECTORY_SEPARATOR . $file;
$class = substr($file,0,strpos($file,".php"));
if (is_subclass_of($class, 'Zend_Controller_Action')) {
$controller = strtolower(substr($file, 0, strpos($file, "Controller")));
$methods = array();
foreach (get_class_methods($class) as $method) {
if (strstr($method,"Action") != false) {
array_push($methods,substr($method,0,strpos($method,"Action")));
}
}
}
$acl[$module][$controller] = $methods;
}
}
}
I have created a function that can get all the actions, controllers and modules from a zend application. Here it is:
$module_dir = substr(str_replace("\\","/",$this->getFrontController()->getModuleDirectory()),0,strrpos(str_replace("\\","/",$this->getFrontController()->getModuleDirectory()),'/'));
$temp = array_diff( scandir( $module_dir), Array( ".", "..", ".svn"));
$modules = array();
$controller_directorys = array();
foreach ($temp as $module) {
if (is_dir($module_dir . "/" . $module)) {
array_push($modules,$module);
array_push($controller_directorys, str_replace("\\","/",$this->getFrontController()->getControllerDirectory($module)));
}
}
foreach ($controller_directorys as $dir) {
foreach (scandir($dir) as $dirstructure) {
if (is_file($dir . "/" . $dirstructure)) {
if (strstr($dirstructure,"Controller.php") != false) {
include_once($dir . "/" . $dirstructure);
}
}
}
}
$default_module = $this->getFrontController()->getDefaultModule();
$db_structure = array();
foreach(get_declared_classes() as $c){
if(is_subclass_of($c, 'Zend_Controller_Action')){
$functions = array();
foreach (get_class_methods($c) as $f) {
if (strstr($f,"Action") != false) {
array_push($functions,substr($f,0,strpos($f,"Action")));
}
}
$c = strtolower(substr($c,0,strpos($c,"Controller")));
if (strstr($c,"_") != false) {
$db_structure[substr($c,0,strpos($c,"_"))][substr($c,strpos($c,"_") + 1)] = $functions;
}else{
$db_structure[$default_module][$c] = $functions;
}
}
}
}
I actually found the best way to have an easily available reflection reference was to recursively tokenise the correct directories and then build an xml document as a result. Caching the xml document for speed and using xpath for retrieving the data.
The plugin builds the reflection xml and caches it for later. I've taken this code out of its original implementation, so its more to give you a feel rather than copy and paste.
Of course, a database works just as well here. But if you're trying to limit your queries per page, a cached xml doc works pretty well.
class My_Reflection_Plugin extends My_Controller_Plugin_Abstract
{
public function routeShutdown(Zend_Controller_Request_Abstract $request)
{
$cache = $this -> getCacheManager() -> getCache('general');
if (!$xml = $cache->load("Reflection"))
{
$paths = array(
PATH_APPLICATION . "/Core",
PATH_SITE . "/Project"
);
foreach ($paths as $path)
{
$this -> inspectDir($path);
}
$cache -> save($this->getReflectionXML(), "Reflection");
}
else
{
$this -> getReflectionXML($xml);
}
}
private function inspectDir($path)
{
$rdi = new RecursiveDirectoryIterator($path);
$rii = new RecursiveIteratorIterator($rdi);
$filtered = new My_Reflection_Filter($rii);
iterator_apply($filtered, array($this, 'process'), array($filtered));
}
private function process($it = false)
{
$this -> getReflectionXML() -> addItem($it -> current());
return true;
}
}
Tokenisation happens inside the filter:
class My_Reflection_Filter extends FilterIterator
{
public function accept()
{
$file = $this->getInnerIterator()->current();
// If we somehow have something other than an SplFileInfo object, just
// return false
if (!$file instanceof SplFileInfo) {
return false;
}
// If we have a directory, it's not a file, so return false
if (!$file->isFile()) {
return false;
}
// If not a PHP file, skip
if ($file->getBasename('.php') == $file->getBasename()) {
return false;
}
// Resource forks are no good either.
if (substr($file->getBaseName(), 0, 2) == '._')
{
return false;
}
$contents = file_get_contents($file->getRealPath());
$tokens = token_get_all($contents);
$file->className = NULL;
$file->classExtends = NULL;
$file->classImplements = array();
$last = null;
while (count($tokens) > 0)
{
$token = array_shift($tokens);
if (!is_array($token))
{
continue;
}
list($id, $content, $line) = $token;
switch ($id)
{
case T_ABSTRACT:
case T_CLASS:
case T_INTERFACE:
$last = 'object';
break;
case T_EXTENDS:
$last = "extends";
break;
case T_IMPLEMENTS:
$last = "implements";
break;
case T_STRING:
switch ($last)
{
case "object":
$file -> className = $content;
break;
case "extends":
$file -> classExtends = $content;
break;
case "implements":
$file -> classImplements[] = $content;
break;
}
break;
case T_WHITESPACE:
// Do nothing, whitespace should be ignored but it shouldnt reset $last.
break;
default:
// If its not directly following a keyword specified by $last, reset last to nothing.
$last = null;
break;
}
}
return true;
}
}
Once you have your reflection xml populated with whatever information you need out of the class, your acl plugin can come after it and query that information with xpath.
I don't think there is a solution for this in Zend. You will have to do it yourself...
One way to do it, is to list all classes, and check if the classes extend (for example) the Zend_Controller_Action class...
check the php functions get_declared_classes and is_subclass_of
foreach(get_declared_classes() as $c){
if(is_subclass_of($c, 'Zend_Controller_Action')){
...
}
}

Categories