Jump to content

Module: Process Changelog


teppo

Recommended Posts

Hi Teppo

Is there anyway to show the changelog table in a template? I want to show it in frontend templates.

You'll need to load up the ProcessChangelog.css file to make it styled correctly (or style yourself), but this does the trick otherwise:

$changelog = $modules->get("ProcessChangelog");
echo $changelog->execute();
  • Like 3
Link to comment
Share on other sites

  • 6 months later...

I've an issue with ProcessChangelog right now which I originally thought was a 3.X issue. Actually, I'm having it across a few 2.7 sites too.

I suspect I had tried to uninstall it the wrong way in the past and that's why I'm getting the following errors

Module 'ProcessChangelogHooks' dependency not fulfilled for: ProcessChangelog

 Unable to install module 'ProcessChangelog': SQLSTATE[23000]: Integrity constraint violation: 1062... 32 secs 

Unable to install module 'ProcessChangelog': SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'changelog-22' for key 'name_parent_id'

When I try to remove it via Modules, I get

Failed to delete module 'ProcessChangelog' 

I'm relatively comfortable with directly editing the database directly via phpMyAdmin.

Is manually deleting the module rows ProcessChangelogHooks the best way forward?

Link to comment
Share on other sites

  • 1 month later...

I was able to add pageviews before and now on a fresh install of PW3 without the previous successful attempt to draw from, here's what I have for ProcessChangelogHooks.module:

Spoiler

<?php

class ProcessChangelogHooks extends WireData implements Module, ConfigurableModule {

    /**
     * Return information about this module (required)
     *
     * @return array
     */
    public static function getModuleInfo() {
        return array(
            'title' => 'Changelog Hooks',
            'summary' => 'Hooks required by Process Changelog for collecting data',
            'href' => 'http://modules.processwire.com/modules/process-changelog/',
            'author' => 'Teppo Koivula',
            'version' => '1.1.3',
            'singular' => true,
            'autoload' => true,
            'requires' => 'ProcessChangelog'
        ); 
    }

    /**
     * Default configuration for this module
     *
     * The point of putting this in it's own function is so that you don't have to specify
     * these defaults more than once.
     *
     * @return array
     */
    static public function getDefaultData() {
        return array(
            'operations' => array(
                "added" => __("added"),
                "moved" => __("moved"),
                "edited" => __("edited"),
                "trashed" => __("trashed"),
                "renamed" => __("renamed"),
                "deleted" => __("deleted"),
                "restored" => __("restored"),
                "published" => __("published"),
                "unpublished" => __("unpublished"),
				"viewed" => __("viewed")
            ),
            'schema_version' => 1,
        );
    }
    
    /**
     * Name and latest schema version for database table used by this module
     *
     */
    const TABLE_NAME = 'process_changelog';
    const SCHEMA_VERSION = 2;

    /**
     * Populate the default config data
     *
     * ProcessWire will automatically overwrite it with anything the user has specifically configured.
     * This is done in construct() rather than init() because ProcessWire populates config data after
     * construct(), but before init().
     *
     */
    public function __construct() {
        foreach(self::getDefaultData() as $key => $value) {
            $this->$key = $value;
        }
    }

    /**
     * Module configuration
     *
     * @param array $data
     * @return InputfieldWrapper
     */
    static public function getModuleConfigInputfields(array $data) {

        // this is a container for fields, basically like a fieldset
        $fields = new InputfieldWrapper();

        // since this is a static function, we can't use $this->modules, so get them from the global wire() function
        $modules = wire('modules');

        // merge default config settings (custom values overwrite defaults)
        $defaults = self::getDefaultData();
        $data = array_merge($defaults, $data);

        // which operations should be tracked?
        $field = $modules->get("InputfieldCheckboxes");
        $field->name = "operations";
        $field->label = __("Operations");
        $field->addOptions($defaults['operations']);
        $field->value = ($data['operations'] === $defaults['operations']) ? array_keys($defaults['operations']) : $data['operations'];
        $field->description = __("You can choose which operations to keep track of here.");
        $field->notes = __("Note that unchecking operations later won't remove rows containing them from database. Instead new rows of those types will no longer be created and existing ones won't be visible anymore.");
        $fields->add($field);

        // should caller (script or URL that triggered this action) be logged?
        $field = $modules->get("InputfieldSelect");
        $field->name = "log_caller";
        $field->label = __("Caller logging");
        $field->description = __("Enable logging of path/URL for script that triggered action?");
        $field->addOptions(array(
            null => __("Disabled"),
            'external' => __("For external callers only (CLI and external applications)"),
            'all' => __('For all callers (CLI, external applications and ProcessWire itself)')
        ));
        $field->notes = __("This can be useful when trying to find out why certain change was triggered. On the other hand it adds to the size of your database table and slightly slows script execution, which is why it's disabled by default.");
        $field->value = isset($data[$field->name]) ? $data[$field->name] : null;
        $fields->add($field);

        // for how long should collected data be retained?
        $field = $modules->get("InputfieldSelect");
        $field->name = "data_max_age";
        $field->label = __("Data max age");
        $field->description = __("For how long should we retain collected data?");
        $field->notes = __("Automatic cleanup requires LazyCron module, which isn't currently installed.");
        if ($modules->isInstalled("LazyCron")) {
            $field->addOptions(array(
                '1 WEEK' => __('1 week'),
                '2 WEEK' => __('2 weeks'),
                '1 MONTH' => __('1 month'),
                '2 MONTH' => __('2 months'),
                '3 MONTH' => __('3 months'),
                '6 MONTH' => __('6 months'),
                '1 YEAR' => __('1 year')
            ));
            $field->notes = __("Leave empty to disable automatic cleanup.");
            $field->value = isset($data[$field->name]) ? $data[$field->name] : null;
        }
        $fields->add($field);

        return $fields;

    }

    /**
     * Initialization function
     *
     * This function attachs required hooks.
     *
     */
    public function init() {

        // update database schema (if not the latest one yet)
        if ($this->schema_version < self::SCHEMA_VERSION) {
            $this->updateDatabaseSchema();
        }

        // remove expired rows daily
        $this->addHook("LazyCron::everyDay", $this, 'cleanup');

        // add hooks that gather information and trigger insert
        $this->pages->addHook('added', $this, 'logPageEvent'); 
        $this->pages->addHook('moved', $this, 'logPageEvent'); 
        $this->pages->addHook('renamed', $this, 'logPageEvent'); 
        $this->pages->addHook('deleted', $this, 'logPageEvent'); 
        $this->pages->addHook('saveReady', $this, 'logPageEvent');
		$this->pages->addHook('viewed', $this, 'logPageEvent');

    }

    /**
     * Initialization when $page is known
     *
     * Attach hook to ProcessPageEdit::buildFormSettings
     *
     */
    public function ready() {
        if ($this->page->template == 'admin' && $this->page->process == 'ProcessPageEdit') {
            $this->addHookAfter('ProcessPageEdit::buildFormSettings', $this, 'hookPageEdit');
        }
    }

    /**
     * Adds a Changelog section to the Settings tab in the page editor
     *
     */
    public function hookPageEdit(HookEvent $event) {
        $form = $event->return;
        $process = $event->object;
        $editPage = $process->getPage();
        try {
            $query = $this->database->prepare("SELECT COUNT(*), MIN(timestamp) FROM " . self::TABLE_NAME . " WHERE pages_id=:id");
            $query->bindValue(':id', $editPage->id, PDO::PARAM_INT); 
            $query->execute();
            list($num_edits, $timestamp) = $query->fetch(PDO::FETCH_NUM);
        } catch(Exception $e) {
            $this->error($e->getMessage());
            $num_edits = 0;
        }
        if ($num_edits) {
            $field = $this->modules->get('InputfieldMarkup');
            $processPage = $this->pages->get('process=' . $this->modules->getModuleID('ProcessChangelog'));
            $field->label = $this->_('Changelog'); // Changelog field label
            $out = sprintf($this->_n('%d edit since %s', '%d edits since %s', $num_edits), $num_edits, $timestamp);
            if ($this->user->hasPermission('changelog')) $out .= " - <a href='$processPage->url?pages_id=$editPage->id'>"
                                                  . $this->_('View History') . "</a>";
            $field->attr('value', "<p>$out</p>");
            $form->append($field);
        }
    }
    
    /**
     * Delete data older than given interval
     *
     * @param string|HookEvent $interval Interval, defaults to data_max_age setting
     */
    public function cleanup($interval = null) {

        if ($interval instanceof HookEvent) $interval = $this->data_max_age;
        if (is_null($interval) && is_null($this->data_max_age)) return;
        else if (is_null($interval)) $interval = $this->data_max_age;

        // @todo check if $interval can be a bound param (not sure it can)
        $interval = $this->database->escapeStr($interval);
        $sql = "DELETE FROM " . self::TABLE_NAME . " WHERE timestamp < DATE_SUB(NOW(), INTERVAL $interval)";
        try {
            $this->database->exec($sql);
        } catch(Exception $e) {
            $this->error($e->getMessage());
        }
    }

    /**
     * Based on event method and other information available this
     * method parses required data and triggers insert method.
     *
     * @param HookEvent $event
     */
        public function logPageEvent(HookEvent $event) {
        // render has no arguments
        if ($event->method == "render") $page = $event->object;
        else $page = $event->arguments[0];

        // don't log operations for repeaters or admin pages
        if ($page instanceof RepeaterPage || $page->template == "admin") return;

        // grab operation from event
        $operation = $event->method;
        if ($operation == "saveReady") $operation = "edited";
        if ($operation == "render") {
            //if ($page->template->name !== "vessel") return;
            $operation = "viewed";
        }

        // only continue if this operation is set to be logged
        if (!in_array($operation, $this->operations)) return;

        $fields_edited = array();
        if ($operation == "edited") {
            // skip new pages or pages being restored/trashed
            if (!$page->id || $page->parentPrevious) return;
            if ($page->isChanged()) {
                foreach ($page->template->fields as $field) {
                    if ($page->isChanged($field->name)) {
                        $fields_edited[] = $field->name;
                    }
                }
                // only continue if at least one field has been changed (or
                // if status has changed trigger new event for that)
                if (!count($fields_edited)) {
                    if ($page->isChanged("status")) {
                        $event->method = $page->is(Page::statusUnpublished) ? "unpublished" : "published";
                        $this->logPageEvent($event);
                    }
                    return;
                }
            } else return;
        } else if ($operation == "renamed") {
            // if previous parent is trash, page is being restored
            if ($page->parentPrevious->id == $this->config->trashPageID) return;
            // if current parent is trash, page is being trashed
            else if ($page->parent->id == $this->config->trashPageID) return;
        } else if ($operation == "moved") {
            if ($page->parent->id == $this->config->trashPageID) {
                // page is being trashed
                $operation = "trashed";
            } else if ($page->parentPrevious->id == $this->config->trashPageID) {
                // page is being restored
                $operation = "restored";
            }
        }

        // details about page being edited, trashed, moved etc.
        $details = array();

        if ($page->title) $details['Page title'] = $page->title;

        $details['Page name'] = $page->name;
        if ($page->namePrevious) {
            $details[($operation == "moved" ? 'Page name' : 'Previous page name')] = $page->namePrevious;
        }

        $details['Template name'] = $page->template->name;
        if ($page->templatePrevious) {
            $details['Previous template name'] = $page->templatePrevious->name;
        }

        $details['Page URL'] = $page->url;
        if ($page->parentPrevious && $operation != "edited") {
            // for pages being edited current or previous parent is irrelevant
            // data since changing parent will also trigger "moved" operation.
            $details['Previous page URL'] = $page->parentPrevious->url;
            if ($page->namePrevious) $details['Previous page URL'] .= $page->namePrevious."/";
            else $details['Previous page URL'] .= $page->name."/";
        }
        if ($operation = "viewed" /*&& $page->template->name == "vessel"*/) {
            $details['Page URL'] .= $event->input->urlSegment(1);
            //echo $pages->get($page)->input->urlSegment(1);//->input->urlSegment(1);
            //print_r($page);
        }

        // note: currently only "edited" operation keeps track of edited fields
        if (count($fields_edited)) $details['Fields edited'] = implode(", ", $fields_edited);

        // find out which script / URL triggered this particular action
        if ($this->log_caller && $caller = $this->getCaller()) $details['Caller'] = $caller;

        $this->insert($operation, $page->id, $page->template->id, $details);

        if ($page->isChanged('status') && !in_array($operation, array("unpublished", "published"))) {
            // if status has changed, log extra unpublished/published event
            $event->method = $page->is(Page::statusUnpublished) ? "unpublished" : "published";
            $this->logPageEvent($event);
        }

    }

 

Somehow with this I'm unable to publish or unpublish pages; if attempted on the pages admin view it just loads forever, on the page editor it reloads the editor blank and doesn't work.

Edited by teppo
Shortened excessively long post by moving code to a spoiler block
Link to comment
Share on other sites

  • 2 weeks later...

@hellomoto, I took a quick look at your code and there are some issues there, but first things first: I'd like to suggest not going this route at all. This module is intended for change tracking, not collecting data for all page views.

I can see at least three problems you will eventually run into here:

  • the amount of data can turn your site unusable (the module is not optimized for this),
  • this module is most likely too simplified for collecting any actually useful statistics, and finally
  • this will slow your site down and make it difficult to cache, since each page load needs to be processed programmatically and results in a database write operation.

You would be much better off with a real analytics software, such as Google Analytics – which is free and provides all the features you will ever need in terms of page views. If you're looking to avoid turning your data to Google, I'd suggest taking a closer look at Piwik, which is a free option.

Now on to the code issues if you still really want to make this thing work. First of all, you're forcing $operation to be "view" by this row:

        if ($operation = "viewed" /*&& $page->template->name == "vessel"*/) {

What you probably meant to use there was "==" instead of "=".

Another issue is related to the row attaching the "viewed" hook:

		$this->pages->addHook('viewed', $this, 'logPageEvent');

There's no "viewed" method to hook into, so what you're looking for is either Page::render or ProcessPageView::execute: first one occurs when a page is being rendered (via API or web interface), second one when a page is being requested online.

Again, I'd like to stress out that in my opinion this addition is not a good idea. Consider yourself warned :)

  • Like 2
Link to comment
Share on other sites

  • 1 month later...

Downloaded this yesterday for a test drive (V 1.2.15). Like it very much. Reporting back with observations and mods.

Disclaimer - I did make another change particular to my needs but I'm pretty certain it doesn't impact anything else.

If the page had a RuntimeMarkup field (kind of a pseudo-field) it was always detected as changed. See ProcessChangelogHooks.

                foreach ($page->template->fields as $field) {
                    //if ($page->isChanged($field->name)) {
                    if (empty($field->runtimeFields) && $page->isChanged($field->name)) {    //SB added condition - ignore RuntimeMarkup field
                        $fields_edited[] = $field->name;
                    }
                }

Also undefined index notices as Adrian mentioned. See ProcessChangelog.

        switch ($operation) {
            case 'renamed':
                if(!empty($details["Previous $key"])) { //SB added if
                    $target .= $details["Previous $key"] 
                        . " <strong>" . __("as") . "</strong> " // as // In context of a rename operation ("renamed old-page-name as new-page-name")
                        . $details[ucfirst($key)];
                    break;
                }
            case 'trashed':
                if(! empty($details["Previous $key"])) { //SB added if
                    $target .= $details["Previous $key"]
                        . " <em>(" . $details[ucfirst($key)] . ")</em>";
                    break;
                }
            default:
                $target .= $details[ucfirst($key)];
        }

The ProcessChangelog buildQuery method was doing things like WHERE operation = '3' instead of using the name of the operation. That made the filter not work.

                    case "operation":
                        if (!in_array($value, array_flip($this->operations))) {
                            unset($this->input->get->$key);
                            break;
                        }
                        if($value) $value = $this->operations[$value - 1];    //SB fix - want word not number
                        else break; //SB fix
                    default:
                        $where[] = "$key $operator '$value'";
                }

Thanks!

  • Like 3
Link to comment
Share on other sites

Thanks, guys! I'm just now taking a closer look at these, and there are a few things I'd like to understand first.

Regarding undefined indexes:

@adrian and @SteveB, it sounds like you were both able to reproduce the undefined index problem. How exactly did you get there? I've been testing this with a multi-language setup and a non-multi-language setup for a while now, and can't seem to figure out the steps to reproduce this.

As far as I can tell, this would mean that a page has been renamed or trashed, but there's no information about what the name used to be. Logically thinking that must be something that shouldn't be logged in the first place, or the problem is in the way I'm fetching the previous page name. Do you have any pointers here, what am I missing? :)

Regarding RuntimeMarkup:

I'm familiar with RuntimeMarkup, but haven't really used it, so my initial thought is that this doesn't sound quite right. A field should not report itself changed every single page load unless it really changes each page load, in which case logging these changes is the only sensible thing to do. Either way, one definitely shouldn't have to check for specific field types like that.

Anyway, I'll take a closer look and see if there's actually a sensible reason why this is happening.

Regarding non-working filters:

@SteveB: If I'm getting this right, selecting an operation on the Changelog page results in a numeric GET param, such as "operation=3", right? I'm not able to reproduce this either, as the values in the operations dropdown are actual operation names, not numbers. Any pointers for reproducing this one?

Thanks again for reporting these issues, and sorry for not being able to reproduce them.. :)

  • Like 2
Link to comment
Share on other sites

One more addition: testing with multi-language page name support, I'm seeing that the rename operations affecting only non-default language are not being logged as renames at all. Currently I'm thinking that this should be logged as a separate rename operation, i.e. if you change the name in multiple languages for the same page at once, it would look in the log a bit like this:

renamed basic-page what as what-2

renamed basic-page what-in-finnish as what-in-finnish-2 

Probably will implement it like that unless someone has a strong opinion on thi :)

  • Like 3
Link to comment
Share on other sites

One idea: if both have the same name (which can happen) it can be clearer that 2 different operations are covered if non default language names are added?

 

renamed basic-page news as news2

renamed basic-page news DE  as news2

(maybe a different bg color for the language part)

Edited by ceberlin
  • Like 2
Link to comment
Share on other sites

Hi Teppo, 

I've been planning to use this module but have not yet spent the time to check it out. Seeing that you are currently working on it, I thought it is hight time to install it :) 

Looks cool so far, but may I suggest a new feature? I use a similar WP plugin which can generate an RSS feed accessible via a URL with a long, "random" hashed string in it, so it is a relatively secure way to get the change info via my RSS reader every 5 minute. Is a simple but powerful tool to get informed of failed login attempts (hackers...) and also to keep an eye on clients in almost "real time" (my reader refreshes the feeds very 5 minute).

A new addition the the above mentioned WP plugin is that it also looks for updates (system, theme, plugins), which is really handy too. This WP plugin relies on WP's lazy cron, but that is generally good enough, I think.

What do you think?

Edited by szabesz
typo
Link to comment
Share on other sites

Teppo,

The undefined indexes thing was only a Notice (harmless) and only when I have debug true in my PW config file.

Re. the filters, the filters form in /setup/changelog has a select returning numeric values. The first thing I did was to put this line in right after where you define $query in buildQuery.

wire('log')->save('messages', 'changelog @' . __LINE__ . ' ' . $query);

Here's results I got (line number will not be same as yours):

No filters: 
changelog @518 SELECT process_changelog.* FROM process_changelog WHERE operation IN('added','moved','edited','trashed','renamed','deleted','restored','published','unpublished') ORDER BY timestamp DESC, id DESC LIMIT 0, 25

'Edited' filter: 
changelog @518 SELECT process_changelog.* FROM process_changelog WHERE operation = '3' ORDER BY timestamp DESC, id DESC LIMIT 0, 25

So I just used the $this->operations array to look up the string from the number the form supplied.

Link to comment
Share on other sites

On 25/09/2016 at 5:38 PM, SteveB said:

The undefined indexes thing was only a Notice (harmless) and only when I have debug true in my PW config file.

Thanks, but I'm still unable to reproduce this. Been running on debug mode, switching between multi-language and non-multi-language setup, so far no idea about this. I'm less worried about a notice and more worried about what caused it -- in this context it could be a sign of a more serious issue somewhere else, one that results in broken or malformed data.

Which version of ProcessWire are you using? Any other tips for reproducing this? Anything you can share would be very much appreciated :) /cc @adrian

On 25/09/2016 at 5:38 PM, SteveB said:

Re. the filters, the filters form in /setup/changelog has a select returning numeric values.

Strangely I'm not able to reproduce this either. The values in the select menu should always be operation names (strings), not numbers. Is there a chance that you might've changed something related to this? Does anyone else see this?

Thanks for clarifying these. As soon as I can figure out what's going on here, I'll be happy to apply a fix :)

Link to comment
Share on other sites

On 25/09/2016 at 2:34 PM, szabesz said:

Looks cool so far, but may I suggest a new feature? I use a similar WP plugin which can generate an RSS feed accessible via a URL with a long, "random" hashed string in it, so it is a relatively secure way to get the change info via my RSS reader every 5 minute. Is a simple but powerful tool to get informed of failed login attempts (hackers...) and also to keep an eye on clients in almost "real time" (my reader refreshes the feeds very 5 minute).

Thanks for the suggestion. To be honest I was going to turn this one down (ProcessChangelog already provides JSON feed, so this could actually be a separate module), but I've already got a rough proof of concept ready and I think this has enough value to be part of ProcessChangelog itself.

Hopefully I can finish this in the next few days (a bit busy right now).

On 25/09/2016 at 2:34 PM, szabesz said:

A new addition the the above mentioned WP plugin is that it also looks for updates (system, theme, plugins), which is really handy too.

Definitely worth considering. Currently this module is limited to keeping track of changed page data, but I'm open for the possibility of tracking more than just that. I'll see if I can find an easy way to handle this (and a sensible way to output such content).

  • Like 1
Link to comment
Share on other sites

Thanks in advance Teppo! I am more than happy to help you by testing if you think I can be of any help this way. I'm also busy-busy-busy too, but I can postpone a few things just to spare some time for this. Since I'm sloooowly moving form WP to PW, I'm looking for ways to keep my useful habits :) 

As for the "output" part of the RSS, here is a screenshot of the WP plugin (called Simple History) to give you an idea what it can look like in an RSS reader (notice the login/logout noticies too, useful to keep track of users of sites with not too many users/logins):

Spoiler

simple-history.png

 

 

  • Like 1
Link to comment
Share on other sites

Hey @teppo - not ignoring your pings, just busy and not really sure why that notice is appearing either. I saw it a couple of times today, but was too rushed to investigate, and now it's not showing, but I will keep an eye out and when I see it when I have a spare moment I'll try to debug.

  • Like 1
Link to comment
Share on other sites

  • 2 weeks later...

Sorry, folks -- looks like I managed to overestimate the amount of time I'd have for this module. Anyway, I've just pushed version 1.3 to Github, along with a new module called ProcessChangelogRSS. Here's the gist of this update:

  • If you access the changelog via /setup/changelog/rss/ you should find an RSS feed for the module. This feed requires the same permissions as the regular changelog view, i.e. you have to be authenticated and have the "changelog" permission.
  • Since the RSS feed might have valid use cases where authentication is an issue, an optional ProcessChangelogRSS module is now included. After installing this module you should go to it's config screen (in the Modules section) and type in a key of your own. After that you can view an RSS feed of changelog events at yourdomain.com/process-changelog-rss.xml?key=your-very-long-and-complex-key.

I don't really know how important this RSS feature is going to be, so didn't want to spend too much time on it -- this is why the description of each item makes use of the same tabular output as the main changelog view itself. Originally I had planned to implement multiple keys and such, but in the end decided to strip all that away and go with the most basic implementation possible.

Feedback on these features would be appreciated, but please note that if you do enable the ProcessChangelogRSS module, the changelog RSS feed is publicly available, even if only for those who somehow gain access to your private key (or are somehow able to guess it). If you are uncomfortable with that, please leave it uninstalled.

  • Like 7
Link to comment
Share on other sites

Another update: the latest version logs multi-language name changes as renames. The implementation is a bit hacky since core doesn't trigger a renamed hook in this case and also doesn't track previous name etc. like it does for the default name field. The output also doesn't mention the language specifically, though that's something that could be added later on.

Please let me know if you have the chance to give this feature a try and happen to notice any inconsistencies. Thanks!

  • Like 6
Link to comment
Share on other sites

Thanks for the updates teppo.  I've noticed that ProcessChangeLog doesn't log when a page (or rather field) is updated from ListerPro. My guess is this has to do something to do the saveReady hook not being called within ListerPro. Ryan posted a solution to 'force' the hook.

p.s. I also posted this in the the Activity Log post by renobird, since I believe it's the same issue.

  • Like 2
Link to comment
Share on other sites

@arjen: thanks for notifying me of this. The solution posted by Ryan a bit later (hooking into both Pages::saveReady and Pages::saveFieldReady) should be relatively easy to implement, but the problem is that Pages::saveFieldReady was added in 2.5.10 and this module currently supports >= 2.2. This could mean either jumping through some extra hoops or dropping support for a couple of releases.

Anyway, I'll take a closer look at this soon. What would've been awesome was if Pages::saveReady worked consistently across all revisions, but it is what it is :)

  • Like 4
Link to comment
Share on other sites

...
    $pwVersion = wire('config')->version;
    if(version_compare($pwVersion, '3.0.35', '>')) {
        $pages->addHookAfter('Pages::savePageOrFieldReady', $this, 'saveReady'); // Pages::savedPageOrField(Page $page, array $changes);
    } else {
        $pages->addHookAfter('Pages::saveReady', $this, 'saveReady');
        if(version_compare($pwVersion, '2.5.9', '>')) {
            $pages->addHookAfter('Pages::saveFieldReady', $this, 'saveReady');
        }
    }
...

 

Edited by horst
last core changes implemented :)
  • Like 4
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...