How do I change the order of files on my server? - php

How do you change the order of files in the file system? I am using Fetch.
The reason I ask is because I have a menu bar that automatically lists the pages in my website by what is in the "pages" folder. The way I see it on fetch, they are in alphabetical order "blog.ctp, games.ctp, home.ctp, news.ctp". So I would expect the menu bar to list the pages as "BLOG GAMES HOME NEWS", but instead it lists them as "GAMES NEWS BLOG HOME". Ultimately I want the order to be "HOME GAMES BLOG NEWS". How do I change the order of the files?
Here is my code in case it is helpful. But my code is not the problem...I just need to know how to change the file order in the folder "Pages".
if ($handle=opendir('../View/Pages/'))
{
while (false !== ($entry = readdir($handle)))
{
if(strpos($entry,".ctp")!==False)
echo "<div class='menubarItem'>".$this->Html->link(substr($entry,0,-4),
array('controller'=>'pages','action'=>'display',substr($entry,0,-4)),
array('escape' => false)).'</div>';
}
}

Add a priority flag to your file names at the beginning or end. I.e. GAMES_1, HOME_2 ...etc... Sort the array of file names using PHP sort() and replace the last two character from file name using substr($filename, -2).

The CakePHP Folder utility has the option to sort the result;
http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::find
App::uses('Folder', 'Utility');
App::uses('Folder', 'Utility');
$myFolder = new Folder('/path/to/directory');
$files = $myFolder->find('*.ctp', true);
foreach ($files as $filename) {
// your code here
}
However, If you do not want to show the pages alphabetically, either prefix the file names with a number and create a special route or don't use the filename for sorting but manually specify the order.
In the end, the only reason for dynamically creating the menu based on the View files is to automatically have the menu generated for you. However, chances are that these changes won't occur often and based on your comment (prefered order) you do have a specific order in mind.
The best solution is to just manually specify the sort order. This will also improve performance as the server will not have to do a directory-scan for each request
For example:
/**
* MenuItems
*
* preferable pass this as a viewVar to the View
*
* file => title
*/
$menuItems = array(
'home' => 'HOME',
'blog' => 'BLOG',
....
);
foreach($menuItems as $file => $title) {
// your code here
}
moving this to a Model
Retrieving the file list inside your View is not the location where this should be done. It's best to read the files beforehand and pass them to the View via a viewvar. Because reading the files is actually 'retrieving data', you may want to create a Model for this that is not connected to a database table.
example
app/Model/Menuoption.php
App::uses('Folder', 'Utility');
class Menuoption extends AppModel {
// this model does not use a database table
public $useTable = false;
public function getOptions()
{
// NOTE: or use the 'manually' created menuItems here
$myFolder = new Folder('/path/to/directory');
return $myFolder->find('*.ctp', true);
}
}
Inside your controller; for example in the beforeRender() callback, or inside a regular action;
public function beforeRender()
{
$this->set('files', ClassRegistry::init('Menuoption')->getOptions());
}
inside your view, you can now access the results via the $files variable;
foreach ($files as $filename) {
// your code here
}

Related

Return files in a directory between a certain date range

I have a directory with a bunch of files in it. I am able to get all the files by using the DirectoryIterator.
$files = new DirectoryIterator('/path/to/directory/');
To add to this, I can also filter which files are returned by using RegexIterator and LimitIterator.
$regex_iterator = new RegexIterator($files, '/my_regex_here/');
$limit_iterator = new LimitIterator($regex_iterator, $offset, $limit);
This works great as it only returns the files I require. Is there a way I could go about only returning files created between a certain date range similar to using the RegexIterator (which filters by matching the regex on a filename)? Think similar to an SQL query:
SELECT * FROM table WHERE created_date BETWEEN 'first_date' AND 'second_date';
I could just loop all the files from $limit_iterator and check when the file was created but I would like to avoid returning unnecessary files from the iterator class as there could potentially be a lot of files in the directory.
I also need, for the sake of pagination, the total count of files based on these filters (before using LimitIterator) so that I can have a 'next' and 'previous' page as required.
Is it possible to do what I am asking for?
I don't think there is a built-in function that does the filtering of dates magically. You can roll up your own though.
Here's an idea via FilterIterator:
class FileDateFilter extends FilterIterator
{
protected $from_unix;
protected $to_unix;
public function __construct($iterator, $from_unix, $to_unix)
{
parent::__construct($iterator);
$this->from_unix = $from_unix;
$this->to_unix = $to_unix;
}
public function accept()
{
return $this->getMTime() >= $this->from_unix && $this->getMTime() <= $this->to_unix;
}
}
So basically accept a FROM and TO arguments like what you normally do in a query.
Just change your logic inside the accept block to your business requirements.
So when you instantiate your custom filter:
$di = new DirectoryIterator('./'); // your directory iterator object
// then use your custom filter class and feed the arguments
$files = new FileDateFilter($di, strtotime('2017-01-01'), strtotime('2018-01-01'));
$total = iterator_count($files);
foreach ($files as $file) {
// now echo `->getFilename()` or `->getMTime()` or whatever you need to do here
}
// or do the other filtering like regex that you have and whatnot
Sidenote: You can use DateTime classes if you choose to do so.

Trying to grab the ID of deleted DB object in an event

I am building an API with Lumen, and this particular part of my application contains the following relationships:
Promotions hasMany Items
Items hasMany images
On an image upload, a row is inserted which provides the URL to the uploaded image. The image is uploaded to public/assets/promotion_$id/item_$id.
When an item is deleted, so too does its respective image folder. That part was relatively easy, and can be done from inside my PromotionController:
public function delete_item(Request $request)
{
$item = PromotionItems::find($request->input('id'))->toArray();
$img_folder_path = 'assets/promotions/promotion_' . $item['promotion_id'] . '/item_' . $item['id'];
if(is_dir($img_folder_path))
{
$files = array_diff(scandir($img_folder_path), array('.','..'));
foreach ($files as $file)
{
unlink($img_folder_path . '/' . $file);
}
rmdir($img_folder_path);
}
$delete = PromotionItems::destroy($request->input('id'));
return response()->json($delete);
}
This is okay, but this also needs to happen when a promotion is deleted. Right now, when a promotion is deleted, an event is called that also deletes items. When an item is deleted, an event is called that also deletes the image rows in the database. So it's a nice, neat chain of events.
But, it will only delete the image folder if an item is being deleted directly, which is why I would like to put this code in my delete event in my item model:
protected static function boot()
{
parent::boot();
static::deleting(function($item)
{
$item->images()->delete();
});
}
However, this function's scope doesn't appear to extend to the actual object that's being deleted. And since the image path contains IDs, I need access to the item's properties before it's deleted.
I'm relatively new to Laravel/Lumen/Eloquent, so any advice would be appreciated.
Edit: I'm aware that I could technically put the folder delete code inside my Promotion controller in its delete function, and just call all the items and loop through them, deleting all their folders/files. But that's redundant code and I'd like to avoid it.
It occurred to me that I'd have to do a folder delete on promotion delete anyway, because I'll want to delete its top level directory. So I just did a sort of recursive function in my controller:
public function remove_directory($path)
{
if(is_dir($path))
{
$files = array_diff(scandir($path), array('.','..'));
foreach ($files as $file)
{
(is_dir("$path/$file")) ? $this->remove_directory("$path/$file") : unlink($path . '/' . $file);
}
rmdir($path);
}
}
So when I call it from my delete_promotion function, it deletes the promotion folder and all folders/files inside of it. And I just call the same function but with the /item_$id on the path in my delete_item function.

Find whether an Image is used anywhere

In my SilverStripe 3.4 environment I have a bunch of different models that have an attached Image, e.g.:
BlogPost has_one Image (via silverstripe/blog)
Widget has_one Image (via silverstripe/widgets)
MyWidget has_one Image (custom module)
I want to prevent an Image e.g. ID 123 from being deleted in the CMS admin if it's used in any of the above (as examples - this should be system wide).
Is there a way that I can check all models that have a related Image at once, maybe via a Image belongs_many_many lookup or something?
You'd prob need to decorate Image via a DataExtension subclass and declare a $belongs_to static array with a custom onBeforeDelete() or possibly validate().
Regardless, within either is where you'd call a routine that checks your database for the necessary conditions. Your choice of which method to use
is dictated by the scenarios under which an Image record might be deleted in your system (e.g you may have some automated, non-Human tasks where the scenario you wish to avoid is played out - so you'd avoid validate() and use onBeforeDelete())
Something like this (Totally untested!)
class MyImageExtension extends DatExtension
{
public function onBeforeDelete()
{
if (!$this->imagesExistThatShouldNotBeDeleted()) {
parent::onBeforeDelete();
}
}
/**
* #return boolean True if images exist that shouldn't be deleted, false otherwise.
*/
private function imagesExistThatShouldNotBeDeleted()
{
$owner = $this->getOwner();
$dataObjectSubClasses = ClassInfo::getValidSubClasses('DataObject');
$classesWithImageHasOne = [];
foreach ($dataObjectSubClasses as $subClass) {
if ($classHasOneImage = $subClass::create()->hasOneComponent('Image')) {
$classesWithImageHasOne[] = $classHasOneImage;
}
}
if (in_array($owner->class, $classesWithImageHasOne)) {
return true;
}
return false;
}
}

Push Array to $data variable to be passed to views

I'm using CI, and for every main page (for example contents) I load a specific css file in the constructor using :
$this->load->vars(array('add_css'=>array('contents')));
In my views, I check if the add_css array exists or not, if yes, I load the contents.css. Now in my controller file (in this case contents.php), I have lots of methods, and while all of them will always load the additional contents.css , specific methods will have (if any) their own add_css too, for example when I'm on method review, I want to load additional rating.css. What I did was :
$data['add_css'] = array('rating');
But it doesn't work because the rating.css overwrites the vars in the constructor.
So is there any way I can push the array? I've tried array_push($data['add_css'],'rating') but it doesn't work.
Thanks
The most elegant way I can think off would be this:
1. In your controller or if you have MY_Controller(it's much better) add new protected (so it is available from all controllers) property that will keep all loaded css files.
protected $_addCss = array();
2. Add new method for setting new css files to this array:
protected function _addCssFile($file)
{
$this->_addCss[] = $file;
return $this; //So you can chain it when adding but not necessary
}
3. New method for manual loading of all added css files.
protected function _loadCssFiles()
{
//Load the vars then reset the property to empty array again
$this->load->vars(array('add_css' => $this->_addCss));
$this->_addCss = array();
return $this;
}
4. In your application controller you can use it like this:
public function __construct()
{
parent::__construct();
$this->_addCssFile('contents');
}
public function test()
{
//Chain the methods so you can add additional css files
$this->_addCssFile('contents')
->_addCssFile('rating')
->_loadCssFiles()
->load->view('test');
}
5. In your view:
echo '<pre>';
print_r($add_css);
echo '</pre>';
Result:
Array
(
[0] => contents
[1] => contents
[2] => rating
)
I had a similar problem. Read what's in the array, write to another array, push new info to it, overwrite the second array to data.

Manage groups in different page with sfDoctrineGuardPlugin

i'm using sfDoctrineGuardPlugin, but i would like a little change, i have 2 groups(admins,users) and i want to manage them separately, what i mean is a page that only shows me the "admins" group and another one the "users" group, i want this because users in "users" group will have some additional info and behavior, i already create a "sfGuardUser" empty module in my backend app, so i can overwrite and add everything i want, i create de actions.class.php
class sfGuardUserActions extends autoSfGuardUserActions
{
public function executeAdmins(sfWebRequest $request)
{
// sorting
if ($request->getParameter('sort') && $this->isValidSortColumn($request->getParameter('sort')))
{
$this->setSort(array($request->getParameter('sort'), $request->getParameter('sort_type')));
}
// pager
if ($request->getParameter('page'))
{
$this->setPage($request->getParameter('page'));
}
$this->pager = $this->getPager();
$this->sort = $this->getSort();
}
}
i copied exactly the "executeIndex" function from "autoSfGuardUserActions" class in cache, and now i can go to guard/admin and it acts like the default one, but now, how can i show only de users from "admins" group?
You have to modify buildQuery() method.

Categories