I'm fairly new to certain programming techniques. Very new to OOP and MVC in general. And I'm pretty sure this is my first StackOverflow question!
I've just downloaded CodeIgniter and have a little project for myself.
I have a list of files and folders on the server and would like to use opendir, readdir and closedir etc to list out them out on a web page in ul's and li's - I've done this in procedural before in a function but have no idea where to even begin with CodeIgniter.
Is there a Helper or Library that already does this? If not, what is the best method? Should I put my code in the model folder?
So confused!
I hope you have learned about MVC architecture already in past year :)
Now about your question. This helper or library you have asked for really exists. For the very same "problem" I have used directory helper and its function directory_map('relative/path/to/your/directory'). This function gets recursively content from your directory and sorts it into array like this
Array
(
[banner] => Array
(
[0] => banner1.jpg
[1] => banner2.jpg
[2] => banner3.jpg
[3] => banner4.jpg
)
[galerie] => Array
(
[0] => 0-PB083393.JPG
[1] => DSCN2897.JPG
[2] => DSCN2908.JPG
[3] => DSCN2917.JPG
[thumb] => Array
(
[0] => 0-PB083393_thumb.JPG
[1] => DSCN2897_thumb.JPG
[2] => DSCN2908_thumb.JPG
)
)
[0] => mapa.jpg
)
which is quite neat and you can use it in - for example - foreach cycle and add ul/li tags.
Probably this question is not relevant after one year, but I hope it could help others.
Ha. This is funny. I was looking for something else and stumbled on to my first ever CI question without realising it was me :D
I've come so far with CI in just less than a month.
I found Directory Helper - directory_map that basically puts your folder structure in to an array of arrays.
I them created a recursive function in the model that turns it in to a proper drop down menu... And when it's a file, it adds in an a href link to that file.
http://ellislab.com/codeigniter/user-guide/helpers/directory_helper.html
If I were doing this, I would:
(1) Create a Library class with a method that takes a directory name and returns an
array of files.
(2) In my controller, I would then load the library and use it to get the list of files for the folder of interest.
(3) I would then load a view while passing the array of file names to the view where
I would assemble the list.
Start by learning how to use the controller to load a view with data (start with a static array). Then learn how to create the library and integrate with your controller.
You might also read up about CodeIgniter's File Helper library unless you want to use the native PHP functions.
Also, learn about PHP unit testing.
I tend to use models for dealing with data from MySQL databases. In your case, you are dealing with information about your file system.
Good luck, CI is a good choice for a PHP/MySQL framework!
First, welcome to CodeIgniter. It rules. Now...
You need a controller function to actually process the directory, similar to this:
public function dir_to_array($dir, $separator = DIRECTORY_SEPARATOR, $paths = 'relative')
{
$result = array();
$cdir = scandir($dir);
foreach ($cdir as $key => $value)
{
if (!in_array($value, array(".", "..")))
{
if (is_dir($dir . $separator . $value))
{
$result[$value] = $this->dir_to_array($dir . $separator . $value, $separator, $paths);
}
else
{
if ($paths == 'relative')
{
$result[] = $dir . '/' . $value;
}
elseif ($paths == 'absolute')
{
$result[] = base_url() . $dir . '/' . $value;
}
}
}
}
return $result;
}
Now you need to call that function to return the results, similar to:
$modules['module_files'] = $this->dir_to_array(APPPATH . 'modules');
This will put the results in a variable called $modules, which you can use in whichever way you want, typically put it in a view like this:
$this->load->view('folder/file', $modules);
If you provide an optional third parameter of TRUE to the load->view function, the result of that view will again be returned for you to use anywhere you like, otherwise it will be echoed out where you call it. The view may look something like this:
<?php
if (isset($module_files) && !empty($module_files))
{
$out = '<ul>';
foreach ($module_files as $module_file)
{
if (!is_array($module_file))
{
// the item is not an array, so add it to the list.
$out .= '<li>' . $module_file . '</li>';
}
else
{
// Looping code here, as you're dealing with a multi-level array.
// Either do recursion (see controller function for example) or add another
// foreach here if you know exactly how deep your nested list will be.
}
}
$out .= '</ul>';
echo $out;
}
?>
I have not checked this for syntax errors, but it should work fine. Hope this helps..
Related
I'm looking for a way to get a list of all available table objects. These are all the classes that are (by default) located under App/Modal/Table and that are handled by TableRegistry. How to get a list of all those objects?
I know it's possible to fetch all tables of the db:
$tables = ConnectionManager::get('default')->schemaCollection()->listTables();
And then using TableRegistry::get() to get the table object.
But this is not possible for my solution, because there are two cases where this does not work:
custom table names that are different to the table object name
plugin table objects
Any ideas?
Edit: Why? I need all table objects that use a behavior X. In my case a custom SearchableBehavior, which updates a searchindex table on each afterSave event for the saved entity. To update the searchindex for all entities of all tables, I need to know which tables are using the SearchableBehavior and call their update method manually.
$tables = glob(APP."Model".DS."Table".DS."*Table.php");
$tablesNames = [];
foreach ($tables as $name){
$item = explode('Table.php', basename($name));
$tablesNames[] = $item[0];
}
pr(tablesNames);
Write an event listener that listens on Model.initialize and then do a check on the subject, which is the table object if the table has your behavior. Then do something with that list.
If this doesn't work for you - you give zero background info - iterate over the apps Model/Table folder and plugin folder and the vendor folders and search for Model folders and check for *Table.php files. Then try to instantiate the table objects based on the path / namespace and filename and check the models. But this is not very fast, you should cache the resulting list.
I recently had a similar use case, where I needed to access all Table Objects, to initialize the data in the database once, in a console command.
I did it by first building an array of all the paths where the Table Object Classes could reside, then iterating over all files and using the ones ending in "Table.php". Note that this approach might need to be modified slightly depending on your use case.
<?php
use Cake\Core\Plugin;
use Cake\ORM\TableRegistry;
use Cake\Filesystem\Folder;
// Building an array of all possible paths. Firstly the src directory:
$tableClassPaths = [
APP . 'Model' . DS . 'Table' . DS,
];
// Secondly, all loaded plugins:
foreach(Plugin::loaded() as $plugin) {
$dir = Plugin::classPath($plugin) . 'Model' . DS . 'Table' . DS;
if(is_dir($dir)) {
$tableClassPaths[$plugin] = $dir;
}
}
// Iterating over each file in each folder.
$tableObjects = [];
foreach($tableClassPaths as $plugin => $dir) {
foreach(new \FilesystemIterator($dir) as $file) {
// All Files ending in Table.php might be relevant
if($file instanceof \SplFileInfo && $file->isFile()
&& mb_substr($file->getFilename(), -9) === 'Table.php'
) {
$className = mb_substr($file->getFilename(), 0, -9);
if(is_string($plugin)) {
$className = $plugin . '.' . $className;
}
$tableObject = TableRegistry::getTableLocator()->get($className);
// Here you can use / filter the Tables, for example by checking for the presence of a behavior "Indexable":
if($tableObject->hasBehavior('Indexable')) {
$tableObjects[] = $tableObject;
}
}
}
}
?>
Keep in mind, that this is only really suitable for very narrow circumstances, since this completely sidesteps the regular MVC patterns of CakePHP.
I managed to fix a problem but I dont understand why it worked and it seems glitchy, so I wonder if someone could explain me. I wanted to get articles from my article models and retrieve that in angular, and I had a hard time getting the subkeys with "featured_images" from octobercms. I found a workaround, like this, in my laravel controller:
public function test()
{
$result = Article::take(4)->get();
$listarr = array();
foreach($result as $article) {
$listarr[] = $article;
foreach($article->featured_images as $image) {
}
}
return response()->json($listarr);
}
But if I remove the foreach($article->featured_images as $image) { } section I dont get the "featured_images" with $listarr. And just using $result doesnt give me those keys if i return response()->json($result);
This is how i want it: http://pastebin.com/MJvnbrrn
But not like this, without "featured_images": http://pastebin.com/1Xa3n9fD
And i get it how i want it if i do that forreach both on $result as $article and only if i then use foreach($article->featured_images as $image) { }. I think I am confused and that there is a more elegant way to this but multidimentional arrays is hard for me.
The foreach call is loading the relationship and therefore including it in the subsequent JSON data. The following call will preload the relationship, using eager loading, and should include it in the same way.
Article::with('featured_images')->take(4)->get();
Alternatively you can use "lazy eager loading"
$result = Article::take(4)->get();
$result->load('featured_images');
I'm having a hard time outputting some array data to an XML file. Here's the workflow:
Get all relevant data (in this case, a collection of videos and the necessary taxonomy) from the DB.
Loop each returned object, cleaning it up a bit (field combinations, etc).
Loop each returned object, returning an XML node by use of a template file (templates/module_name_xml_entity.tpl.php).
Put all the XML nodes into a wrapper XML template (templates/module_name_xml_wrapper.tpl.php).
Save the wrapper (now including the repeated nodes) into a file on the filesystem.
I have been able to complete this workflow if I manually write XML inline (eg: $xml .= ' ' . $data['field'] . '';
That's not optimal however, and I've been asked to use render arrays instead (and to keep my template files within the module).
So, #'s 1, 2, 5 I can figure out (since saving a file is the same). It's #3 that is the real bugger.
My code:
The dump from the db query results in an array of video objects (title, thumbnail, tags, etc). I convert that to the following:
Array (
[#template] => module_name_xml_entry,
[#video] => stdClass Object (
[title], [thumbnail]....
),
[#theme] => module_name_xml_entry,
)...
Now here's something interesting: if I dd() the array (there's 990 of them), I see that "#children" and "#printed" has been added automagically, therefore I assume I'm working with a real render array.
I then try every darn way I can think of to convert this array into XML. I've tried $xmlOut .= render($theStuffAbove), drupal_render($youguessedit), please_lord_make_it_go($facepalm)... no avail.
What I get out is either blank (nothing is in $xmlOut) or the array itself.
Again, I can loop my DB results, convert the result into XML manually (string building mess) and save all that out just fine. It's the using of render arrays that baffles me. Reading "TDGD7" hasn't helped (there's only a few short pages out of 1047 on render arrays), and I'm just not understanding how render arrays can be "rendered."
Update:
I forgot to mention I do have a module_name_theme(...) function setup:
$items = array();
$items['module_name_admin_settings_form'] = array(...);
$items['module_name_xml_wrapper'] = array(
'variables' => array('videos' => NULL)),
'template' => 'templates/module_name_xml_wrapper',
);
$items['module_name_xml_entity'] = array(
'variables' => array('video' => array()),
'template' => 'templates/module_name_xml_entity',
);
return $items;
Fixed
Ok, so this is odd (swear I'd done this before).
Changed template filenames to use dashes instead of underscores (eg: 'templates/module_name_xml_entry.tpl.php -> module-name-xml-entry.tpl.php)
Changed reference in the hook_theme() to use the dashed names instead
Inside my functions, I used a $variables = array('video' => $video);
I called $output[] = theme('module_name_xml_entry', $variables);
I used $output in my wrapper (module_name_xml_wrapper) to save to the filesystem.
So the long and short: it looks like cleaning up the theme() function and my hook_theme() made the output work finally.
Please help, this is my first plugin I'm writing and I'm completely lost. I'm trying to write and update information in a table in a joomla database using my custom giveBadge() function. The functions receives two different variables, the first variable is the $userID and the second one is the digit 300 which I pass at the bottom of the class using giveBadge(300). At the same comparing the $userID in the Joomla database to ensure that the number 300 is given to the current user logged in the Joomla site.
Thanks in advance.
<?php
defined('JPATH_BASE') or die;
class plgUserBadge extends JPlugin
{
public function onUserLogin () {
$user =& JFactory::getUser();
$userID =& user->userID;
return $userID;
}
public function giveBadge ($userID, &$badgeID) {
// Get a db connection.
$db = JFactory::getDbo();
// Create a new query object.
$query = $db->getQuery(true);
// Fields to update.
$fields = array(
'profile_value=\'Updating custom message for user 1001.\'',
'ordering=2');
// Conditions for which records should be updated.
$conditions = array(
'user_id='.$userID,
'profile_key=\'custom.message\'');
$query->update($db->quoteName('#__user_badges'))->set($fields)->where($conditions);
$db->setQuery($query);
try {
$result = $db->query();
} catch (Exception $e) {
// Catch the error.
}es = array(1001, $db->quote('custom.message'), $db->quote('Inserting a record using insert()'), 1);
}
}
giveBadge(300); //attaches to $badgeID
?>
Here is not going well with your code:
You can drop the assign by reference in all your code (&) - you really don't need it, in 99% of the cases.
Use an IDE (for example Eclipse with PDT). At the top of your code you have & user->userID; Any IDE will spot your error and also other things in your code.
Study existing plugins to understand how they work. Here is also the documentation on plugins.
The method onUserLogin() will automatically be called by Joomla when the specific event is triggered (when your plugin is activated). Check with a die("My plugin was called") to see if your plugin is really called
inside onUserLogin() you do all your business logic. You are not supposed to return something, just return true. Right now your method does absolutely nothing. But you can call $this->giveBadge() to move the logic to another method.
If I have a person model with first_name and last_name, how do I create and display a full_name? I would like to display it at the top of my Edit and View views (i.e. "Edit Frank Luke") and other places. Simply dropping echoes to first_name and last_name isn't DRY.
I'm sorry if this is a very simple question, but nothing has yet worked.
Thank you,
Frank Luke
Edit for clarity: Okay, I have a function on the person model.
function full_name() {
return $this->Person->first_name . ' ' . $this->Person->last_name;
}
In the view, I call
echo $person['Person']['full_name']
This gives me a notice that I have an undefined index. What is the proper way to call the function from the view? Do I have to do it in the controller or elsewhere?
If what you are wanting is just to display a full name, and never need to do any database actions (comparisons, lookups), I think you should just concatenate your fields in the view.
This would be more aligned with the MVC design pattern. In your example you just want to view information in your database in a different way.
Since the action of concatenating is simple you probably don't save much code by placing it in a separate function. I think its easiest to do just in the view file.
If you want to do more fancy things ( ie Change the caps, return a link to the user ) I would recommend creating an element which you call with the Users data.
The arrays set by the save() method only return fields in the datbase, they do not call model functions. To properly use the function above (located in your model), you will need to add the following:
to the controller, in the $action method:
$this->set( 'fullname', $this->Person->full_name();
// must have $this-Person->id set, or redefine the method to include $person_id
in the view,
echo $fullname;
Basically, you need to use the controller to gather the data from the model, and assign it to the controller. It's the same process as you have before, where you assign the returned data from the find() call to the variable in the view, except youre getting the data from a different source.
There are multiple ways of doing this. One way is to use the afterFind-function in a model-class.
See: http://book.cakephp.org/view/681/afterFind.
BUT, this function does not handle nested data very well, instead, it doesn't handles it al all!
Therefore I use the afterfind-function in the app_model that walks through the resultset
function afterFind($results, $primary=false){
$name = isset($this->alias) ? $this->alias : $this->name;
// see if the model wants to attach attributes
if (method_exists($this, '_attachAttributes')){
// check if array is not multidimensional by checking the key 'id'
if (isset($results['id'])) {
$results = $this->_attachAttributes($results);
} else {
// we want each row to have an array of required attributes
for ($i = 0; $i < sizeof($results); $i++) {
// check if this is a model, or if it is an array of models
if (isset($results[$i][$name]) ){
// this is the model we want, see if it's a single or array
if (isset($results[$i][$name][0]) ){
// run on every model
for ($j = 0; $j < sizeof($results[$i][$name]); $j++) {
$results[$i][$name][$j] = $this->_attachAttributes($results[$i][$name][$j]);
}
} else {
$results[$i][$name] = $this->_attachAttributes($results[$i][$name]);
}
} else {
if (isset($results[$i]['id'])) {
$results[$i] = $this->_attachAttributes($results[$i]);
}
}
}
}
}
return $results;
}
And then I add a _attachAttributes-function in the model-class, for e.g. in your Person.php
function _attachAttributes($data) {
if (isset($data['first_name']) && isset($data['last_name'])) {
$data['full_name'] = sprintf("%s %s %s", $data['first_name'], $data['last_name']);
}
return $data;
}
This method can handle nested modelData, for e.g. Person hasMany Posts then this method can also attachAttributes inside the Post-model.
This method also keeps in mind that the linked models with other names than the className are fixed, because of the use of the alias and not only the name (which is the className).
You must use afterFind callback for it.
You would probably need to take the two fields that are returned from your database and concatenate them into one string variable that can then be displayed.
http://old.nabble.com/Problems-with-CONCAT-function-td22640199.html
http://teknoid.wordpress.com/2008/09/29/dealing-with-calculated-fields-in-cakephps-find/
Read the first one to find out how to use the 'fields' key i.e. find( 'all', array( 'fields' => array( )) to pass a CONCAT to the CakePHP query builder.
The second link shows you how to merge the numeric indexes that get returned when you use custom fields back into the appropriate location in the returned results.
This should of course be placed in a model function and called from there.