Jump to content

Marc

Members
  • Posts

    103
  • Joined

  • Last visited

Posts posted by Marc

  1. I have added a custom settings page to my admin using the ready.php method described here. The page I added under admin has the ProcessPageEdit process and its ID get swapped with another page in ready.php. This works fine but when I click on the new button, it does not get the 'active' state, unlike the standard admin buttons like 'pages', 'settings', etc, which get a CSS class making them white (depending on default theme settings) so you can see where you are in the topnav menu. I would like this for my custom button as well, but how? Is there perhaps an API call to do this in ready.php?

    Here's my ready.php:

    if ($page->template == "admin" && $page->name == "settings-appearance")
    {
        $input->get->id = $pages->get("/settings-appearance/")->id;
    }

    Screenshot:

    button.jpg

  2. I have a bunch of fields that have a label that is overwritten by the context of the template the fields belong to. So outside of the template these field have a different label from inside the template. I want to keep the outside label intact and overwrite the template-specific labels with the API. So how would I turn the code below into something that sets a new label for only the template context?
     

    $f->set("label", "new value");
    
    $f->save();

     

  3. Revising the problematic part of the code I posted in the OP, here's the new and improved part:

    if ($field->type == "FieldtypePage") {
    
        $table = wire('fields')->get($field->id)->getTable();
        $query = wire('database')->query("SELECT
            tbl.pages_id,
            tbl.data,
            COUNT(*)
            FROM $table AS tbl
            INNER JOIN pages AS p
            ON tbl.pages_id = p.id
            INNER JOIN templates AS t
            ON p.templates_id = t.id
            WHERE t.id = '{$template->id}'
            GROUP BY data"
        );
        $ids = $query->fetchAll(PDO::FETCH_NUM); // Multidimensional array (2 levels) [0] => array(2047, 4419, 12). The second level corresponds to the selected columns in the query.
    
        // Add options that are in use by our products to a whitelist.
        foreach ($ids as $item) {
            $whitelist[]= $item[1];
        }
    
        $options = $inputfield->getInputfield()->getOptions(); // Returns simple assoc array [4777] => dieren, [4778] => tweeling, etc.
    
        // Remove unsued options from the field.
        foreach ($options as $pageID => $pageTitle) {
            if ( !in_array($pageID, $whitelist) ) {
                $inputfield->getInputfield()->removeOption($pageID);
            }
        }
    
        if (!count($ids)) continue; // No pages are using any of the option of this field, so don't add it to our form.
    }
    

    This shaves off about 100 ms of of 150 ms, so it's pretty darn fast compared to the way I did it before. I suppose maintaining a counter as suggested by BitPoet may be even faster, I'll have to compare it someday. Thanks everyone!  :D

    • Like 2
  4. You've a page field for each of your categories ($field in your code). The snippet of ryan then retrieves the database table of that field and does fetch all the saved id's in that table. Meaning all the pages, which are selected in that field on any page with that field. I'm not sure if you're using these page fields in multiple templates. If so you'd need to extend the query to limit the retrieved pages to only those selected on pages of a specific template.

    To get the number of uses per option you'd add the following to the query: "SELECT data, COUNT(*) FROM $table GROUP BY data;" This will return a table of id's and their number of occurrences. 

    All in all this would limit your queries to one per page-field (per template; if multiple).

    I had a go and this is what I came up with (I have to take the template into consideration like you said):

    $table = wire('fields')->get($field->id)->getTable();
    $query = wire('database')->query("SELECT
        tbl.pages_id,
        tbl.data, p.id,
        p.templates_id,
        t.id
        FROM $table AS tbl
        INNER JOIN pages AS p
        ON tbl.pages_id = p.id
        INNER JOIN templates AS t
        ON p.templates_id = t.id
        WHERE t.id = '{$template->id}'
        GROUP BY data"
    );
    $ids = $query->fetchAll(PDO::FETCH_COLUMN);
    $items = wire('pages')->getById($ids);
    
    foreach (wire('pages')->get($field->parent_id)->children() as $option) { // Parent_id is the parent page whose child pages are listed as options for this field.
        $frequency = $items->find("{$field->name}={$option->id}")->count(); // Count pages using the field and current option, and of course the template of the correct product (which is any child of the current page).
    
        if ($frequency == 0) $inputfield->getInputfield()->removeOption($option->id); // Remove the option from the field.
    }
    

    This saves about 20 milliseconds (of 150), so it's not as efficient as I'd hoped. Unless I'm doing it wrong?

  5. I don't think I understand, is that supposed to give you a pageArray with all the pages using the options of a given field?

    $table = $fields->get('your_page_field')->getTable();
    $query = $database->query("SELECT data FROM $table GROUP BY data");
    $ids = $query->fetchAll(PDO::FETCH_COLUMN);
    $items = $pages->getById($ids); 
    
    // $items is a PageArray
    echo $items->implode("\n", "<a href='{url}'>{title}</a>");
    

    I tried to apply it to my situation but I get an array with the options of my fields. Not sure how to properly implement this:

    if ($field->type == "FieldtypePage") {
        $table = $field->getTable();
        $query = wire('database')->query("SELECT data FROM $table GROUP BY data");
        $ids = $query->fetchAll(PDO::FETCH_COLUMN);
        $items = wire('pages')->getById($ids);
        echo $items->implode("\n", "<a href='{url}'>{title}</a>");
        
        $i = 0;
        foreach (wire('pages')->get($field->parent_id)->children() as $option) { // Parent_id is the parent page whose child pages are listed as options for this field.
            $frequency = $items->count("{$field->name}={$option->id},template=$template"); // Count pages using the field and current option, and of course the template of the correct product (which is any child of the current page).
            echo "$frequency<br>";
            $i = $i+$frequency;
            if ($frequency == 0) $inputfield->getInputfield()->removeOption($option->id); // Remove the option from the field.
        }
        if ($i == 0) continue; // No pages are using any of the option of this field, so don't add it to our form.
    }
    
  6. $fields = $template->fieldgroup->find('name^=_meta');

    and save the substr check later on, maybe this also improves performance a bit by reducing the loop iterations. not 100% sure about that, maybe it needs to be

    That's a nice little tip, works just as you described. Looks like it shaved off a few milliseconds too  :)

    edit: ah, scrap that, I just see that you're using inputField($p) <-- (where does $p come from?)

    $p = new Page(). I do this because I want blanc input fields, not pre-filled inputs from my product pages.

    I can think of two possibilites to approach this:

    1. Reverse your logic and add the products in a page field to the option page (not really intuitive)
    2. Add a counter field to the option pages' template(s) and update the value through a hook on Pages::save for your product template (with a similar looping logic as in your code above). Then you can easily filter with $optionpages->children("usecount>0").

    Suggestion nr 1 would be highly unpractical for the person entering new products, but yeah I see how it would speed things up. Your second suggestion sounds like good idea, I will look into it!

  7. This one is tough to explain in one line so please bear with me. I'm building a list of about 700 (paginated) products. You can filter the list based on the fields of the products' template (they all have the same template). There's a field for color, material, theme, etc. These are page fields, so their options are stored as pages. So page 'color' has child pages blue, red, white, etc. The filter gets all the fields from the template and adds them to a nice PW form automatically, so I don't have to list anything manually. This is the part I have working.

    What I am adding is a way to exclude any option that is not currently used by any product. So if no product has the color red in its color field, I want to remove that option. I have managed to get this working also, but the way I'm doing seems wrong to me, as I'm doing a query for every option of every field, so if I have 10 fields with 5 options each, that is 50 queries and it is adding about 150 ms to my page load. So my question is, is there a more efficient way to check which page options are not in use by my product pages? Are there other obvious mistakes I'm making? Here's what I've got so far, it's part of a function so I'm wiring some things in:

    $template = wire('templates')->get($page->child()->template);
    $fields = $template->fieldgroup;
    foreach($fields as $field) {
        $inputfield = $field->getInputfield($p);
        if (in_array($inputfield->name, $ignorefields)) continue;
        if (substr($inputfield->name, 0, 5) !== "meta_") continue; // Only list those whose name starts with "meta_".
        $inputfield->description = ""; // Remove descriptions.
    
        // Now check if this field is stored in our $inputs array. If it is, check the type of this field and act accordingly.
        if (array_key_exists($field->name, $inputs)) {
            if ($field->type == "FieldtypeCheckbox") {
                if ($inputs[$field->name] == 1) {
                    $inputfield->attr('checked', 'checked');
                }
            }
            else if ($field->type == "FieldtypePage") {
                $values = $inputs[$field->name];
                $inputfield->attr('value', $values);
            }
        }
    
    /** THIS IS WHERE THINGS COULD HOPEFULLY BE IMPROVED **/
        if ($field->type == "FieldtypePage") {
            $i = 0;
            foreach (wire('pages')->get($field->parent_id)->children() as $option) { // Parent_id is the parent page whose child pages are listed as options for this field.
                $frequency = wire('pages')->count("{$field->name}={$option->id},template={$page->child()->template}"); // Count pages using the field and current option, and of course the template of the correct product (which is any child of the current page).
                $i = $i+$frequency;
                if ($frequency == 0) $inputfield->getInputfield()->removeOption($option->id); // Remove the option from the field.
            }
            if ($i == 0) continue; // No pages are using any of the option of this field, so don't add it to our form.
        }
    
        $form->add($inputfield);
    }
    
  8. Hi Marc, if it isn't reproducible it can be hard to track down. I haven't tried it with the latest 2.7.2 master version. What version of PW do you use?

    I'm using the latest master version, so version 2.7.2. I don't understand why the PW developers would not be able to reproduce it though, as I first encountered it with my current project and still encounter it doing a brand new, unmodified install just for testing this bug.

  9. Don't forget about the "Add Children" permission on the parent template.

    That would be the user template in this case, which already has all permissions checked. The pages are actually created in the correct location, but they are not added to the pageTable array so I have to add them manually after the page is saved. (I get the "Children were found that may be added to this table" message in that case.)

  10. I am trying to add information about users of my website to a pageTable field called 'user_notes', which I added to the 'user' template. The parent of the pageField pages is a user, so every user can have child pages with a 'note' template. Adding new notes to this pageTable works great when I'm logged in as the superuser, but when I try to do this logged in as a 'planner' user, the child pages are created but they are not added to the pageTable array. I'm not sure why, as I've been looking at every involved permission setting and I just can't seem to find out what might be going wrong. Here are some details:

    • I have created a user role called 'planner'. It has all the 'page-edit' (sub)permissions.
    • I have a pageTable field called 'user_notes'.
    • The pageTable 'user_notes' creates pages under a 'user' with a 'note' template. The 'planner' role has all permissions to edit/create pages using this 'note' template.
  11. Just thought I'd add the solution to this problem, for reference. Turns out there's a file called /site/assets/cache/LazyCron.lock (or something along those lines) preventing the cron functionality from working when there are PHP errors in my cron logic, which can happen during development of course. Fixing the errors and deleting the lock file restores functionality. 

    • Like 8
  12. Looks like I still don't quite understand. In a regular template file I added these tests, trying to remove a page from 'user_notes' which is a PageArray field:

    $note = $pages->get(1259);
    $customer = $note->parent;
    
    $customer->user_notes->remove($note);
    $customer->of(false);
    
    $customer->save('user_notes');

    ^ Result: no error, but PageArray is not updated.

    $note = $pages->get(1258);
    $customer = $note->parent;
    
    $customer->of(false);
    $customer->user_notes->remove($note);
    
    $customer->save('user_notes');

    ^ Result: "Field 'user_notes' from page 1172 is not saveable because it is a formatted value."

    $note = $pages->get(1258);
    $customer = $note->parent;
    
    $customer->user_notes->remove($note);
    $customer->of(false);
    $customer->user_notes->remove($note);
    
    $customer->save('user_notes');
    

    ^ Result: PageArray is successfully updated.

    Why would I have to use the 'remove' method twice in order for this to work?

  13. I have a template file that receives some form data that I save to a pageTable field. I use the pageTable to add notes to user profiles, so every user can have child pages containing a note and a date. Here's what I use:

    $notes = $input->post->textarea('notes'); // Passed via javascript.
    $customerId = $input->post->int('id');
    $customer = $users->get($customerId);
    
    $newNote = new Page;
    $newNote->template = 'user-notes'; 
    $newNote->parent = $customer; 
    $newNote->of(false); // turn off output formatting before setting values
    $newNote->save();
        
    $newNote->title = 'user note';
    $newNote->notes_date = date('Y-m-d H:i:s');
    $newNote->notes = $notes;
    $newNote->save();
    
    // So far so good, but this bit does not work:
    $customer->user_notes->add($newNote);
    $customer->of(false);
    $customer->save('user_notes');
    // Just as an example, this part DOES work:
    $customer->gender = 2;
    $customer->save('gender');

    The child page is created but it is not added to the user's 'user_notes' pageTable. If I place this code in my admin module, it works as expected. Any ideas what could be going wrong?

    Edit: fixed it by switching these lines around:

    $customer->of(false);
    $customer->user_notes->add($newNote);

    Now it works as expected. Strange it works in the other order on another page, but there you go.

  14. Thanks for the suggestions guys, but it turned out not to be a cache or permission problem: I had set the page to 'unpublished' because I want nobody but an automated cron job to use that page. Publishing the page enables lazy cron to start working.

    Edit: it worked a few times (as in the log was updated with a few new entries), but now it stopped working. Refreshing the page with the cronjobs.php template does absolutely nothing as far as the lazy cron is concerned  :( If I write to the log file directly in this template, it works just fine, for example wire('log')->save("cronjobs", "Cronjobs template executed.");

    Edit 2: the cron started working again after I put some extra output in the cronjobs.php template. So it seems like if there is no output in a template (e.g. echo "hi"), the template file won't be processed. This is in the default PW multilanguage setup (with _main.php and _init.php automatically prepended to templates).

  15. I have a template called cronjobs.php. All it has (for now) is this super simple laze cron cal that attempts to write an entry to a processwire log file:

    function cronTest(HookEvent $e) {
    
        wire('log')->save("cronjobs", "Cronjobs executed.");
    
    }
    
    // add a hook to your function:
    wire()->addHook('LazyCron::every30Seconds', null, 'cronTest');
    

    This does not work (no entry is ever added to the 'cronjobs' log file (despite me refreshing the page with the cronjobs.php template a hundred times). 

    Am I doing something wrong?

  16. How about this in the execute function?

    if($this->config->ajax && $this->input->post->message){
      $this->message($this->input->post->message);
      return;
    }
    

    Well yeah, that's the PHP part of the ajax function, but to display the actual message without refreshing the page some javascript magic will have to happen. I'm sorry if I didn't make myself clear, but the real question is: is there a build in javascript function I can use to display these messages? I would like to use that instead of my custom functionality if possible. Like there is for the SystemNotifications module, as Ryan explains here. I tried that module but for my purposes it's not the right fit.

  17. I'm working on an admin module and I'm using the build in notification system for messages, errors and warnings, which works great. Some of my forms are handled by ajax however, and it would be nice if there was a javascript equivalent of $page->message("hi there"), like there is for the SystemNotifications module. (Which works like Notifications.message("Message to send").) I currently have a custom notification system for ajax in place, but it would be nice if I could use something purely PW. Can this be done?

×
×
  • Create New...