Jump to content
teppo

Module: Process Changelog

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

Share this post


Link to post
Share on other sites

Yup, thanks Adrian! Had completely missed this one, but glad it (apparently) got solved :)

  • Like 1

Share this post


Link to post
Share on other sites

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?

Share this post


Link to post
Share on other sites

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

Share this post


Link to post
Share on other sites

@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

Share this post


Link to post
Share on other sites

Hey @teppo - just noticed this notice - I think it might be new since installing multi-language support on the site.

PHP Notice: Undefined index: Previous page name in .../modules/ProcessChangelog/ProcessChangelog.module:574
 

Share this post


Link to post
Share on other sites

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

Share this post


Link to post
Share on other sites

Quick question: is there an "official" update planned or should one try the above patch from SteveB?

Share this post


Link to post
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

Share this post


Link to post
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

Share this post


Link to post
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

Share this post


Link to post
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

Share this post


Link to post
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.

Share this post


Link to post
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 :)

Share this post


Link to post
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

Share this post


Link to post
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

Share this post


Link to post
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

Share this post


Link to post
Share on other sites

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

Share this post


Link to post
Share on other sites

Thanks a lot Teppo, I do appreciate the effort! I'm gonna start using it and report my findings as soon as I have enough experience.

  • Like 1

Share this post


Link to post
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

Share this post


Link to post
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

Share this post


Link to post
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

Share this post


Link to post
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

Share this post


Link to post
Share on other sites

No problem @teppo. Thanks for taking a look.  It's something we ran into a while ago. Since I'm testing all the modules which are tracking changes I've ran into this again ;) Thought you should know. 

Thank you @horst for stepping in!

  • Like 2

Share this post


Link to post
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

  • Recently Browsing   0 members

    No registered users viewing this page.

  • Similar Content

    • By bernhard
      --- Please use RockFinder3 ---
    • By MoritzLost
      Cacheable Placeholders
      This module allows you to have pieces of dynamic content inside cached output. This aims to solve the common problem of having a mostly cacheable site, but with pieces of dynamic output here and there.  Consider this simple example, where you want to output a custom greeting to the current user:
      <h1>Good morning, <?= ucfirst($user->name) ?></h1> This snippet means you can't use the template cache (at least for logged-in users), because each user has a different name. Even if 99% of your output is static, you can only cache the pieces that you know won't include this personal greeting. A more common example would be CSRF tokens for HTML forms - those need to be unique by definition, so you can't cache the form wholesale.
      This module solves this problem by introducing cacheable placeholders - small placeholder tokens that get replaced during every request. The replacement is done inside a Page::render hook so it runs during every request, even if the response is served from the template cache. So you can use something like this:
      <h1>Good morning, {{{greeting}}}</h1> Replacement tokens are defined with a callback function that produces the appropriate output and added to the module through a simple hook:
      // site/ready.php wire()->addHookAfter('CachePlaceholders::getTokens', function (HookEvent $e) { $tokens = $e->return; $tokens['greeting'] = [ 'callback' => function (array $tokenData) { return ucfirst(wire('user')->name); } ]; $e->return = $tokens; }); Tokens can also include parameters that are parsed and passed to the callback function. There are more fully annotated examples and step-by-step instructions in the README on Github!
      Features
      A simple and fast token parser that calls the appropriate callback and runs automatically. Tokens may include multiple named or positional parameters, as well as multi-value parameters. A manual mode that allows you to replace tokens in custom pieces of cached content (useful if you're using the $cache API). Some built-in tokens for common use-cases: CSRF-Tokens, replacing values from superglobals and producing random hexadecimal strings. The token format is completely customizable, all delimiters can be changed to avoid collisions with existing tag parsers or template languages. Links
      Github Repository & documentation Module directory (pending approval) If you are interested in learning more, the README is very extensive, with more usage examples, code samples and usage instructions!
    • By Craig
      I've been using Fathom Analytics for a while now and on a growing number of sites, so thought it was about time there was a PW module for it.
      WayFathomAnalytics
      WayFathomAnalytics is a group of modules which will allow you to view your Fathom Analytics dashboard in the PW admin panel and (optionally) automatically add and configure the tracking code on front-end pages.
      Links
      GitHub Readme & documentation Download Zip Modules directory Module settings screenshot What is Fathom Analytics?
      Fathom Analytics is a simple, privacy-focused website analytics tool for bloggers and businesses.

      Stop scrolling through pages of reports and collecting gobs of personal data about your visitors, both of which you probably don't need. Fathom is a simple and private website analytics platform that lets you focus on what's important: your business.
      Privacy focused Fast-loading dashboards, all data is on a single screen Easy to get what you need, no training required Unlimited email reports Private or public dashboard sharing Cookie notices not required (it doesn't use cookies or collect personal data) Displays: top content, top referrers, top goals and more
    • By daniels
      This is a lightweight alternative to other newsletter & newsletter-subscription modules.
      You can find the Module in the Modules directory and on Github
      It can subscribe, update, unsubscribe & delete a user in a list in Mailchimp with MailChimp API 3.0. It does not provide any forms or validation, so you can feel free to use your own. To protect your users, it does not save any user data in logs or sends them to an admin.
      This module fits your needs if you...
      ...use Mailchimp as your newsletter / email-automation tool ...want to let users subscribe to your newsletter on your website ...want to use your own form, validation and messages (with or without the wire forms) ...don't want any personal user data saved in any way in your ProcessWire environment (cf. EU data regulation terms) ...like to subscribe, update, unsubscribe or delete users to/from different lists ...like the Mailchimp UI for creating / sending / reviewing email campaigns *I have only tested it with PHP 7.x so far, so use on owners risk
      EDIT:
      Since 0.0.4, instructions and changelog can be found in the README only. You can find it here  🙂
      If you have questions or like to contribute, just post a reply or create an issue or pr on github, thanks!
    • By MoritzLost
      Sorry for the convoluted title. I have a problem with Process modules that define a custom page using the page key through getModuleInfo (as demonstrated in this excellent tutorial by @bernhard). Those pages are created automatically when the module is installed. The problem is that the title of the page only gets set in the current language. That's not a problem if the current language (language of the superuser who is installing the module) is the default language; if it isn't, the Process page is missing a title in the default language. This has the very awkward effect that a user using the backend in the default language (or any other language) will see an empty entry in the setup menu:

      This screenshot comes from my Cache Control module which includes a Process page. Now I realize the description sounds obscure, but for us it's a common setup: We a multiple bilingual sites where the default language is German and the second language is English. While the clients use the CMS in German, as a developer I prefer the English interface, so whenever I install a Process module I get this problem.
      As a module author, is there a way to handle this situation? I guess it would be possible to use post-installation hooks or create the pages manually, but I very much prefer the declarative approach. The page title is already translatable (through the __ function), but of course at the time of installation there is no translation, and as far as I'm aware it's not possible to ship translations with a module so they are used automatically. Could this situation be handled better in the core? I would prefer if the module installation process would always set the title of the Process page in the default language, instead of the language of the current user.
×
×
  • Create New...